8604f98a4bbf2d7e896a9bbed4f9fe1eab83ab98
[payproc.git] / src / commands.c
1 /* commands.c - Handle a client request.
2  * Copyright (C) 2014, 2015, 2017 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 #include <config.h>
21
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25
26
27 #include "util.h"
28 #include "logging.h"
29 #include "payprocd.h"
30 #include "stripe.h"
31 #include "paypal.h"
32 #include "journal.h"
33 #include "session.h"
34 #include "currency.h"
35 #include "preorder.h"
36 #include "protocol-io.h"
37 #include "mbox-util.h"
38 #include "commands.h"
39
40 /* Helper macro for the cmd_ handlers.  */
41 #define set_error(a,b)                          \
42   do {                                          \
43     err = gpg_error (GPG_ERR_ ## a);            \
44     conn->errdesc = (b);                        \
45   } while (0)
46
47
48 /* Object describing a connection.  */
49 struct conn_s
50 {
51   unsigned int idno;     /* Connection id for logging.  */
52   int fd;                /* File descriptor for this connection.  */
53   estream_t stream;      /* The corresponding stream object.  */
54                          /* N.B. The stream object may only be used
55                             by the connection thread.  */
56   char *command;         /* The command line (malloced). */
57   keyvalue_t dataitems;  /* The data items.  */
58   const char *errdesc;   /* Optional description of an error.  */
59 };
60
61
62 \f
63 /* Allocate a new conenction object and return it.  Returns
64    NULL on error and sets ERRNO.  */
65 conn_t
66 new_connection_obj (void)
67 {
68   static unsigned int counter;
69   conn_t conn;
70
71   conn = xtrycalloc (1, sizeof *conn);
72   if (conn)
73     {
74       conn->idno = ++counter;
75       conn->fd = -1;
76     }
77   return conn;
78 }
79
80 /* Initialize a connection object which has been allocated with
81    new_connection_obj.  FD is the file descriptor for the
82    connection.  */
83 void
84 init_connection_obj (conn_t conn, int fd)
85 {
86   conn->fd = fd;
87 }
88
89
90 /* Shutdown a connection.  This is used by asynchronous calls to tell
91    the client that the request has been received and processing will
92    continue.  */
93 void
94 shutdown_connection_obj (conn_t conn)
95 {
96   if (conn->stream)
97     {
98       es_fclose (conn->stream);
99       conn->stream = NULL;
100     }
101   if (conn->fd != -1)
102     {
103       close (conn->fd);
104       conn->fd = -1;
105     }
106 }
107
108
109 /* Release a connection object.  */
110 void
111 release_connection_obj (conn_t conn)
112 {
113   if (!conn)
114     return;
115
116   shutdown_connection_obj (conn);
117
118   xfree (conn->command);
119   keyvalue_release (conn->dataitems);
120   xfree (conn);
121 }
122
123
124 /* Return the file descriptor for the conenction CONN.  */
125 int
126 fd_from_connection_obj (conn_t conn)
127 {
128   return conn->fd;
129 }
130
131
132 unsigned int
133 id_from_connection_obj (conn_t conn)
134 {
135   return conn->idno;
136 }
137
138
139 static void
140 write_data_value (const char *value, estream_t fp)
141 {
142   if (!value)
143     value = "";
144   for ( ; *value; value++)
145     {
146       if (*value == '\n')
147         {
148           if (value[1])
149             es_fputs ("\n ", fp);
150         }
151       else
152         es_putc (*value, fp);
153     }
154   es_putc ('\n', fp);
155 }
156
157
158 static void
159 write_data_line (keyvalue_t kv, estream_t fp)
160 {
161   const char *value;
162
163   if (!kv)
164     return;
165
166   value = kv->value;
167   if (!value)
168     return;
169   es_fputs (kv->name, fp);
170   es_fputs (": ", fp);
171   write_data_value (value, fp);
172
173   if (opt.debug_client)
174     log_debug ("client-rsp: %s: %s\n", kv->name, kv->value? kv->value:"");
175 }
176
177
178 static void
179 write_data_line_direct (const char *name, const char *value, estream_t fp)
180 {
181   if (!value)
182     return;
183   es_fputs (name, fp);
184   es_fputs (": ", fp);
185   write_data_value (value, fp);
186
187   if (opt.debug_client)
188     log_debug ("client-rsp: %s: %s\n", name, value);
189 }
190
191
192 static void
193 write_ok_line (estream_t fp)
194 {
195   es_fputs ("OK\n", fp);
196   if (opt.debug_client)
197     log_debug ("client-rsp: OK\n");
198 }
199
200
201 static void
202 write_ok_linef (estream_t fp, const char *format, ...)
203 {
204   va_list arg_ptr;
205   char *buffer;
206
207   va_start (arg_ptr, format);
208   buffer = gpgrt_vbsprintf (format, arg_ptr);
209   va_end (arg_ptr);
210
211   es_fprintf (fp, "OK %s\n", buffer? buffer : "[out of core]");
212   if (opt.debug_client)
213     log_debug ("client-rsp: OK %s\n", buffer? buffer : "[out of core]");
214   es_free (buffer);
215 }
216
217
218 static void
219 write_err_line (gpg_error_t err, const char *desc, estream_t fp)
220 {
221   es_fprintf (fp, "ERR %d (%s)\n",
222               err, desc? desc : gpg_strerror (err));
223   if (opt.debug_client)
224     log_debug ("client-rsp: ERR %d (%s)\n",
225                err, desc? desc : gpg_strerror (err));
226 }
227
228
229 static void
230 write_rem_line (const char *comment, estream_t fp)
231 {
232   es_fprintf (fp, "# %s\n", comment);
233   if (opt.debug_client)
234     log_debug ("client-rsp: # %s\n", comment);
235 }
236
237
238 static void
239 write_rem_linef (estream_t fp, const char *format, ...)
240 {
241   va_list arg_ptr;
242   char *buffer;
243
244   va_start (arg_ptr, format);
245   buffer = gpgrt_vbsprintf (format, arg_ptr);
246   va_end (arg_ptr);
247
248   es_fprintf (fp, "# %s\n", buffer? buffer : "[out of core]");
249   if (opt.debug_client)
250     log_debug ("client-rsp: # %s\n", buffer? buffer : "[out of core]");
251   es_free (buffer);
252 }
253
254
255 \f
256 /* SESSION is a multipurpose command to help implement a state-full
257    service.  Note that the state information is intentional not
258    persistent and thus won't survive a daemon restart.
259
260    The following sub-commands are available:
261
262    create [TTL]
263
264      Create a new session
265
266      A new session is created and the provided data dictionary is
267      stored by payprocd for future requests.  The data dictionary is
268      optional.  On success the returned data has an "_SESSID" item
269      which is to be used for all further requests.  If TTL has been
270      given this is used instead of the defaul TTL value.
271
272    destroy SESSID
273
274      Destroy a session.
275
276      This shall be used to free the internal storage required for the
277      session and to avoid leaving sensitive information in RAM.
278
279    get SESSID
280
281      Get data from a session.
282
283      Return the data stored in the session identified by SESSID.
284
285    put SESSID
286
287      Put data into a session.
288
289      Store or update the given data in the session.  Deleting an item
290      from the session dictionary is possible by putting an empty
291      string for it.
292
293    alias SESSID
294
295      Create an alias for the session.
296
297      On success the returned data has an "_ALIASID" item which is to
298      be used for all further alias related requests.
299
300    dealias ALIASID
301
302      Destroy the given ALIAS.
303
304      This does not destroy the session.
305
306    sessid ALIASID
307
308      Return the session id for an alias.
309
310      On success the returned data has an "_SESSID" item.
311
312  */
313 static gpg_error_t
314 cmd_session (conn_t conn, char *args)
315 {
316   gpg_error_t err;
317   keyvalue_t kv;
318   char *options;
319   char *sessid = NULL;
320   char *aliasid = NULL;
321   char *errdesc;
322
323   if ((options = has_leading_keyword (args, "create")))
324     {
325       int ttl = atoi (options);
326       err = session_create (ttl, conn->dataitems, &sessid);
327       keyvalue_release (conn->dataitems);
328       conn->dataitems = NULL;
329     }
330   else if ((options = has_leading_keyword (args, "get")))
331     {
332       keyvalue_release (conn->dataitems);
333       conn->dataitems = NULL;
334       err = session_get (options, &conn->dataitems);
335     }
336   else if ((options = has_leading_keyword (args, "put")))
337     {
338       err = session_put (options, conn->dataitems);
339       if (gpg_err_code (err) == GPG_ERR_ENOMEM)
340         {
341           /* We are tight on memory - better destroy the session so
342              that the caller can't try over and over again.  */
343           session_destroy (options);
344         }
345       keyvalue_release (conn->dataitems);
346       conn->dataitems = NULL;
347     }
348   else if ((options = has_leading_keyword (args, "destroy")))
349     {
350       err = session_destroy (options);
351       keyvalue_release (conn->dataitems);
352       conn->dataitems = NULL;
353     }
354   else if ((options = has_leading_keyword (args, "alias")))
355     {
356       err = session_create_alias (options, &aliasid);
357       keyvalue_release (conn->dataitems);
358       conn->dataitems = NULL;
359     }
360   else if ((options = has_leading_keyword (args, "dealias")))
361     {
362       err = session_destroy_alias (options);
363       keyvalue_release (conn->dataitems);
364       conn->dataitems = NULL;
365     }
366   else if ((options = has_leading_keyword (args, "sessid")))
367     {
368       keyvalue_release (conn->dataitems);
369       conn->dataitems = NULL;
370       err = session_get_sessid (options, &sessid);
371     }
372   else
373     {
374       write_err_line (1, "Unknown sub-command", conn->stream);
375       write_rem_line ("Supported sub-commands are:", conn->stream);
376       write_rem_line ("  create [TTL]",    conn->stream);
377       write_rem_line ("  get SESSID",      conn->stream);
378       write_rem_line ("  put SESSID",      conn->stream);
379       write_rem_line ("  destroy SESSID",  conn->stream);
380       write_rem_line ("  alias SESSID",    conn->stream);
381       write_rem_line ("  dealias ALIASID", conn->stream);
382       write_rem_line ("  sessid ALIASID",  conn->stream);
383       return 0;
384     }
385
386   switch (gpg_err_code (err))
387     {
388     case GPG_ERR_LIMIT_REACHED:
389       errdesc = "Too many active sessions or too many aliases for a session";
390       break;
391     case GPG_ERR_NOT_FOUND:
392       errdesc = "No such session or alias or session timed out";
393       break;
394     case GPG_ERR_INV_NAME:
395       errdesc = "Invalid session or alias id";
396       break;
397     default: errdesc = NULL;
398     }
399
400   if (err)
401     write_err_line (err, errdesc, conn->stream);
402   else
403     {
404       write_ok_line (conn->stream);
405       write_data_line_direct ("_SESSID", sessid, conn->stream);
406       write_data_line_direct ("_ALIASID", aliasid, conn->stream);
407       for (kv = conn->dataitems; kv; kv = kv->next)
408         if (kv->name[0] >= 'A' && kv->name[0] < 'Z')
409           write_data_line (kv, conn->stream);
410     }
411   xfree (sessid);
412   xfree (aliasid);
413   return err;
414 }
415
416
417 \f
418 /* The CARDTOKEN command creates a token for a card.  The following
419    values are expected in the dataitems:
420
421    Number:     The number of the card
422    Exp-Year:   The expiration year (2014..2199)
423    Exp-Month:  The expiration month (1..12)
424    Cvc:        The CVS number (100..9999)
425    Name:       Name of the card holder (optional)
426
427    On success these items are returned:
428
429    Token:     The one time use token
430    Last4:     The last 4 digits of the card for display
431    Live:      Set to 'f' in test mode or 't' in live mode.
432  */
433 static gpg_error_t
434 cmd_cardtoken (conn_t conn, char *args)
435 {
436   gpg_error_t err;
437   keyvalue_t dict = conn->dataitems;
438   keyvalue_t kv;
439   const char *s;
440   int aint;
441
442   (void)args;
443
444   s = keyvalue_get_string (dict, "Number");
445   if (!*s)
446     {
447       set_error (MISSING_VALUE, "Credit card number not given");
448       goto leave;
449     }
450
451   s = keyvalue_get_string (dict, "Exp-Year");
452   if (!*s || (aint = atoi (s)) < 2014 || aint > 2199 )
453     {
454       set_error (INV_VALUE, "Expiration year out of range");
455       goto leave;
456     }
457
458   s = keyvalue_get_string (dict, "Exp-Month");
459   if (!*s || (aint = atoi (s)) < 1 || aint > 12 )
460     {
461       set_error (INV_VALUE, "Invalid expiration month");
462       goto leave;
463     }
464
465   s = keyvalue_get_string (dict, "Cvc");
466   if (!*s || (aint = atoi (s)) < 100 || aint > 9999 )
467     {
468       set_error (INV_VALUE, "The CVC has not 2 or 4 digits");
469       goto leave;
470     }
471
472   err = stripe_create_card_token (&conn->dataitems);
473
474  leave:
475   if (err)
476     {
477       write_err_line (err, conn->errdesc, conn->stream);
478       write_data_line (keyvalue_find (conn->dataitems, "failure"),
479                        conn->stream);
480       write_data_line (keyvalue_find (conn->dataitems, "failure-mesg"),
481                        conn->stream);
482     }
483   else
484     write_ok_line (conn->stream);
485   for (kv = conn->dataitems; kv; kv = kv->next)
486     if (kv->name[0] >= 'A' && kv->name[0] < 'Z')
487       write_data_line (kv, conn->stream);
488
489   return err;
490 }
491
492
493 \f
494 /* The CHARGECARD command charges the given amount to a card.  The
495  * following values are expected in the dataitems:
496  *
497  * Amount:     The amount to charge with optional decimal fraction.
498  * Currency:   A 3 letter currency code (EUR, USD, GBP, JPY)
499  * Recur:      An optional recurrence interval: 0 = not recurring,
500  *             1 = yearly, 4 = quarterly, 12 = monthly.
501  * Card-Token: The token returned by the CARDTOKEN command.
502  * Capture:    Optional; defaults to true.  If set to false
503  *             this command creates only an authorization.
504  *             The command CAPTURECHARGE must then be used
505  *             to actually charge the card. [currently ignored]
506  * Desc:       Optional description of the charge.
507  * Stmt-Desc:  Optional string to be displayed on the credit
508  *             card statement.  Will be truncated at about 15 characters.
509  * Email:      Optional contact mail address of the customer.
510  *             For recurring donations this is required.
511  * Meta[NAME]: Meta data further described by NAME.  This is used to convey
512  *             application specific data to the log file.
513  *
514  * On success these items are returned:
515  *
516  * Charge-Id:  The ID describing this charge
517  * Live:       Set to 'f' in test mode or 't' in live mode.
518  * Currency:   The currency of the charge.
519  * Amount:     The charged amount with optional decimal fraction.
520  * Recur:      0 or the Recur value.  To cope with too small amounts,
521  *             this and then also Amount may be changed from the request.
522  * account-id: Our account id for recurring payments
523  * _timestamp: The timestamp as written to the journal
524  *
525  */
526 static gpg_error_t
527 cmd_chargecard (conn_t conn, char *args)
528 {
529   gpg_error_t err;
530   keyvalue_t dict = conn->dataitems;
531   keyvalue_t kv;
532   const char *s;
533   unsigned int cents;
534   int decdigs;
535   char *buf = NULL;
536   int recur;
537
538   (void)args;
539
540   /* Get Recurrence value or replace by default.  */
541   s = keyvalue_get_string (dict, "Recur");
542   if (!valid_recur_p (s, &recur))
543     {
544       set_error (MISSING_VALUE, "Invalid value for 'Recur'");
545       goto leave;
546     }
547   err = keyvalue_putf (&conn->dataitems, "Recur", "%d", recur);
548   dict = conn->dataitems;
549   if (err)
550     goto leave;
551
552   /* Get currency and amount.  */
553   s = keyvalue_get_string (dict, "Currency");
554   if (!valid_currency_p (s, &decdigs))
555     {
556       set_error (MISSING_VALUE, "Currency missing or not supported");
557       goto leave;
558     }
559
560   s = keyvalue_get_string (dict, "Amount");
561   if (!*s || !(cents = convert_amount (s, decdigs)))
562     {
563       set_error (MISSING_VALUE, "Amount missing or invalid");
564       goto leave;
565     }
566   err = keyvalue_putf (&conn->dataitems, "_amount", "%u", cents);
567   dict = conn->dataitems;
568   if (err)
569     goto leave;
570
571   /* We only support the use of a card token and no direct supply of
572      card details.  This makes it easier to protect or audit the
573      actual credit card data.  The token may only be used once.  */
574   s = keyvalue_get_string (dict, "Card-Token");
575   if (!*s)
576     {
577       set_error (MISSING_VALUE, "Card-Token missing");
578       goto leave;
579     }
580
581   if (recur)
582     {
583       /* Let's ask Stripe to create a subscription.  */
584       s = keyvalue_get_string (dict, "Email");
585       if (!is_valid_mailbox (s))
586         {
587           set_error (MISSING_VALUE,
588                      "Recurring payment but no valid 'Email' given");
589           goto leave;
590         }
591
592       /* Find or create a plan.  */
593       err = stripe_find_create_plan (&conn->dataitems);
594       dict = conn->dataitems;
595       if (err)
596         {
597           conn->errdesc = "error creating a Plan";
598           goto leave;
599         }
600
601       /* Create a Subscription using the just plan from above and the
602        * Card-Token supplied to this command.  */
603       err = stripe_create_subscription (&conn->dataitems);
604       dict = conn->dataitems;
605       if (err)
606         {
607           conn->errdesc = "error creating a Subscription";
608           goto leave;
609         }
610     }
611   else
612     {
613       /* Let's ask Stripe to process it.  */
614       err = stripe_charge_card (&conn->dataitems);
615       if (err)
616         goto leave;
617     }
618
619   buf = reconvert_amount (keyvalue_get_int (conn->dataitems, "_amount"),
620                           decdigs);
621   if (!buf)
622     {
623       err = gpg_error_from_syserror ();
624       conn->errdesc = "error converting _amount";
625       goto leave;
626     }
627   err = keyvalue_put (&conn->dataitems, "Amount", buf);
628   if (err)
629     goto leave;
630
631   jrnl_store_charge_record (&conn->dataitems, PAYMENT_SERVICE_STRIPE, recur);
632
633  leave:
634   if (err)
635     {
636       write_err_line (err, conn->errdesc, conn->stream);
637       write_data_line (keyvalue_find (conn->dataitems, "failure"),
638                        conn->stream);
639       write_data_line (keyvalue_find (conn->dataitems, "failure-mesg"),
640                        conn->stream);
641     }
642   else
643     write_ok_line (conn->stream);
644   for (kv = conn->dataitems; kv; kv = kv->next)
645     if (kv->name[0] >= 'A' && kv->name[0] < 'Z')
646       write_data_line (kv, conn->stream);
647   write_data_line (keyvalue_find (conn->dataitems, "account-id"), conn->stream);
648   if (!err)
649     write_data_line (keyvalue_find (conn->dataitems, "_timestamp"),
650                      conn->stream);
651   es_free (buf);
652   return err;
653 }
654
655
656 \f
657 /* The PPCHECKOUT does a PayPal transaction.  Depending on the
658  * sub-command different data items are required.
659  *
660  * The following sub-commands are available:
661  *
662  * prepare
663  *
664  *   Start a checkout operation.  In this mode the data is collected,
665  *   an access code fetched from paypal, and a redirect URL returned
666  *   to the caller.  Required data:
667  *
668  *   Amount:     The amount to charge with optional decimal fraction.
669  *   Currency:   A 3 letter currency code (EUR, USD, GBP, JPY)
670  *   Recur:      An optional recurrence interval: 0 = not recurring,
671  *               1 = yearly, 4 = quarterly, 12 = monthly.
672  *   Desc:       Optional description of the charge.
673  *   Meta[NAME]: Meta data further described by NAME.  This is used
674  *               to convey application specific data to the log file.
675  *   Return-Url: URL to which Paypal redirects.
676  *   Cancel-Url: URL to which Paypal redirects on cancel.
677  *   Session-Id: Id of the session to be used for storing state.  If this
678  *               is not given a new session will be created.
679  *   Paypal-Xp:  An optional Paypal Experience Id.
680  *
681  *   On success these items are returned:
682  *
683  *   _SESSID:    If Session-Id was not supplied the id of a new session
684  *               is returned.
685  *   Redirect-Url: The caller must be redirected to this URL for further
686  *                 processing.
687  *
688  * execute
689  *
690  *   Finish a Paypal checkout operation.  Required data:
691  *
692  *   Alias-Id:     The alias id used to access the state from the
693  *                 prepare command.  This should be retrieved from the
694  *                 Return-Url's "aliasid" parameter which has been
695  *                 appended to the Return-Url by the prepare sub-command.
696  *   Paypal-Payer: Returned by Paypal for non-recurring subscriptions via
697  *                 the Return-Url's "PayerID" parameter.
698  *
699  *   On success these items are returned:
700  *
701  *   Charge-Id:  The ID describing this charge (not for subscriptions)
702  *   Live:       Set to 'f' in test mode or 't' in live mode.
703  *   Currency:   The currency of the charge.
704  *   Amount:     The charged amount with optional decimal fraction.
705  *   Email:      The mail address as told by Paypal.
706  *   account-id: Our account id for recurring payments.
707  *   _timestamp: The timestamp as written to the journal
708  *
709  */
710 static gpg_error_t
711 cmd_ppcheckout (conn_t conn, char *args)
712 {
713   gpg_error_t err;
714   char *options;
715   int decdigs;
716   char *newsessid = NULL;
717   keyvalue_t dict = conn->dataitems;
718   keyvalue_t kv;
719   const char *s;
720   int execmode = 0;
721
722   if ((options = has_leading_keyword (args, "prepare")))
723     {
724       int recur;
725
726       /* Get Recurrence value or replace by default.  */
727       s = keyvalue_get_string (dict, "Recur");
728       if (!valid_recur_p (s, &recur))
729         {
730           set_error (MISSING_VALUE, "Invalid value for 'Recur'");
731           goto leave;
732         }
733       err = keyvalue_putf (&conn->dataitems, "Recur", "%d", recur);
734       dict = conn->dataitems;
735       if (err)
736         goto leave;
737
738       /* Get currency and amount.  */
739       s = keyvalue_get_string (dict, "Currency");
740       if (!valid_currency_p (s, &decdigs))
741         {
742           set_error (MISSING_VALUE, "Currency missing or not supported");
743           goto leave;
744         }
745
746       s = keyvalue_get_string (dict, "Amount");
747       if (!*s || !convert_amount (s, decdigs))
748         {
749           set_error (MISSING_VALUE, "Amount missing or invalid");
750           goto leave;
751         }
752
753       /* Create a session if no session-id has been supplied.  */
754       s = keyvalue_get_string (dict, "Session-Id");
755       if (!*s)
756         {
757           err = session_create (0, NULL, &newsessid);
758           if (err)
759             goto leave;
760           err = keyvalue_put (&conn->dataitems, "Session-Id", newsessid);
761           if (err)
762             goto leave;
763           dict = conn->dataitems;
764         }
765
766       if (recur)
767         {
768           /* Let's ask Paypal to create a subscription.  */
769           s = keyvalue_get_string (dict, "Email");
770           if (!is_valid_mailbox (s))
771             {
772               set_error (MISSING_VALUE,
773                          "Recurring payment but no valid 'Email' given");
774               goto leave;
775             }
776
777           /* Find or create a plan.  */
778           err = paypal_find_create_plan (&conn->dataitems);
779           dict = conn->dataitems;
780           if (err)
781             {
782               conn->errdesc = "error creating a Plan";
783               goto leave;
784             }
785
786           /* Create a Subscription using the just plan from above and
787            * the Approval supplied to this command.  */
788           err = paypal_create_subscription (&conn->dataitems);
789           dict = conn->dataitems;
790           if (err)
791             {
792               conn->errdesc = "error creating a Subscription";
793               goto leave;
794             }
795         }
796       else
797         {
798           /* Let's ask Paypal to process it.  */
799           err = paypal_checkout_prepare (&conn->dataitems);
800           if (err)
801             goto leave;
802           dict = conn->dataitems;
803         }
804
805     }
806   else if ((options = has_leading_keyword (args, "execute")))
807     {
808       execmode = 1;
809
810       err = paypal_checkout_execute (&conn->dataitems);
811       if (err)
812         goto leave;
813       dict = conn->dataitems;
814       jrnl_store_charge_record (&conn->dataitems, PAYMENT_SERVICE_PAYPAL,
815                                 keyvalue_get_int (conn->dataitems, "Recur"));
816       dict = conn->dataitems;
817     }
818   else
819     {
820       write_err_line (1, "Unknown sub-command", conn->stream);
821       write_rem_line ("Supported sub-commands are:", conn->stream);
822       write_rem_line ("  prepare", conn->stream);
823       write_rem_line ("  execute", conn->stream);
824       return 0;
825     }
826
827  leave:
828   if (err)
829     {
830       write_err_line (err, conn->errdesc, conn->stream);
831       write_data_line (keyvalue_find (conn->dataitems, "failure"),
832                        conn->stream);
833       write_data_line (keyvalue_find (conn->dataitems, "failure-mesg"),
834                        conn->stream);
835     }
836   else
837     {
838       write_ok_line (conn->stream);
839     }
840
841   for (kv = conn->dataitems; kv; kv = kv->next)
842     if ((!execmode && !strcmp (kv->name, "Redirect-Url"))
843         || (execmode && (!strcmp (kv->name, "Charge-Id")
844                           || !strcmp (kv->name, "Live")
845                           || !strcmp (kv->name, "Email")
846                           || !strcmp (kv->name, "Currency")
847                           || !strcmp (kv->name, "Amount"))))
848       write_data_line (kv, conn->stream);
849
850   if (execmode)
851     write_data_line (keyvalue_find (conn->dataitems, "account-id"),
852                      conn->stream);
853
854   if (!err)
855     {
856       write_data_line_direct ("_SESSID", newsessid, conn->stream);
857       write_data_line (keyvalue_find (conn->dataitems, "_timestamp"),
858                        conn->stream);
859     }
860   xfree (newsessid);
861   return err;
862 }
863
864
865 \f
866 /* The SEPAPREORDER command adds a preorder record for a SEPA payment
867    into the preorder database.  The following values are expected in
868    the dataitems:
869
870    Amount:     The amount to charge with optional decimal fraction.
871    Currency:   If given its value must be EUR.
872    Recur:      Optional recurrence value.
873    Desc:       Optional description of the charge.
874    Email:      Optional contact mail address of the customer
875    Meta[NAME]: Meta data further described by NAME.  This is used to convey
876                application specific data to the log file.
877
878    On success these items are returned:
879
880    Sepa-Ref:   A string to be returned to the caller.
881    Amount:     The reformatted amount
882    Currency:   The value "EUR"
883
884  */
885 static gpg_error_t
886 cmd_sepapreorder (conn_t conn, char *args)
887 {
888   gpg_error_t err;
889   keyvalue_t dict = conn->dataitems;
890   keyvalue_t kv;
891   const char *s;
892   unsigned int cents;
893   char *buf = NULL;
894
895   (void)args;
896
897   /* Get currency and amount.  */
898   s = keyvalue_get (dict, "Currency");
899   if (!s)
900     {
901       err = keyvalue_put (&conn->dataitems, "Currency", "EUR");
902       if (err)
903         goto leave;
904       dict = conn->dataitems;
905     }
906   else if (strcasecmp (s, "EUR"))
907     {
908       set_error (INV_VALUE, "Currency must be \"EUR\" if given");
909       goto leave;
910     }
911
912   s = keyvalue_get_string (dict, "Amount");
913   if (!*s || !(cents = convert_amount (s, 2)))
914     {
915       set_error (MISSING_VALUE, "Amount missing or invalid");
916       goto leave;
917     }
918   err = keyvalue_putf (&conn->dataitems, "_amount", "%u", cents);
919   dict = conn->dataitems;
920   if (err)
921     goto leave;
922   buf = reconvert_amount (keyvalue_get_int (conn->dataitems, "_amount"), 2);
923   if (!buf)
924     {
925       err = gpg_error_from_syserror ();
926       conn->errdesc = "error converting _amount";
927       goto leave;
928     }
929   err = keyvalue_put (&conn->dataitems, "Amount", buf);
930   if (err)
931     goto leave;
932
933   /* Note that the next function does not only store the record but
934      also creates the SEPA-Ref value and puts it into dataitems.  This
935      is to make sure SEPA-Ref is a unique key for the preorder db.  */
936   err = preorder_store_record (&conn->dataitems);
937
938  leave:
939   if (err)
940     {
941       write_err_line (err, conn->errdesc, conn->stream);
942       write_data_line (keyvalue_find (conn->dataitems, "failure"),
943                        conn->stream);
944       write_data_line (keyvalue_find (conn->dataitems, "failure-mesg"),
945                        conn->stream);
946     }
947   else
948     write_ok_line (conn->stream);
949   for (kv = conn->dataitems; kv; kv = kv->next)
950     if (kv->name[0] >= 'A' && kv->name[0] < 'Z')
951       write_data_line (kv, conn->stream);
952
953   es_free (buf);
954   return err;
955 }
956
957
958 /* The COMMITPREORDER command updates a preorder record and logs the data.
959
960    Sepa-Ref:   The key referencing the preorder
961    Amount:     The actual amount of the payment.
962    Currency:   If given its value must be EUR.
963
964    On success these items are returned:
965
966    Sepa-Ref:   The Sepa-Ref string
967    XXX:        FIXME:
968
969  */
970 static gpg_error_t
971 cmd_commitpreorder (conn_t conn, char *args)
972 {
973   gpg_error_t err;
974   keyvalue_t dict = conn->dataitems;
975   unsigned int cents;
976   keyvalue_t kv;
977   const char *s;
978   char *buf = NULL;
979
980   (void)args;
981
982   s = keyvalue_get_string (dict, "Sepa-Ref");
983   if (!*s)
984     {
985       set_error (MISSING_VALUE, "Key 'Sepa-Ref' not given");
986       goto leave;
987     }
988
989   /* Get currency and amount.  */
990   s = keyvalue_get (dict, "Currency");
991   if (!s)
992     {
993       err = keyvalue_put (&conn->dataitems, "Currency", "EUR");
994       if (err)
995         goto leave;
996       dict = conn->dataitems;
997     }
998   else if (strcasecmp (s, "EUR"))
999     {
1000       set_error (INV_VALUE, "Currency must be \"EUR\" if given");
1001       goto leave;
1002     }
1003
1004   s = keyvalue_get_string (dict, "Amount");
1005   if (!*s || !(cents = convert_amount (s, 2)))
1006     {
1007       set_error (MISSING_VALUE, "Amount missing or invalid");
1008       goto leave;
1009     }
1010   err = keyvalue_putf (&conn->dataitems, "_amount", "%u", cents);
1011   dict = conn->dataitems;
1012   if (err)
1013     goto leave;
1014   buf = reconvert_amount (keyvalue_get_int (conn->dataitems, "_amount"), 2);
1015   if (!buf)
1016     {
1017       err = gpg_error_from_syserror ();
1018       conn->errdesc = "error converting _amount";
1019       goto leave;
1020     }
1021   err = keyvalue_put (&conn->dataitems, "Amount", buf);
1022   if (err)
1023     goto leave;
1024
1025   err = preorder_update_record (conn->dataitems);
1026
1027  leave:
1028   if (err)
1029     {
1030       write_err_line (err, conn->errdesc, conn->stream);
1031       write_data_line (keyvalue_find (conn->dataitems, "failure"),
1032                        conn->stream);
1033       write_data_line (keyvalue_find (conn->dataitems, "failure-mesg"),
1034                        conn->stream);
1035     }
1036   else
1037     {
1038       write_ok_line (conn->stream);
1039       for (kv = conn->dataitems; kv; kv = kv->next)
1040         if (kv->name[0] >= 'A' && kv->name[0] < 'Z')
1041           write_data_line (kv, conn->stream);
1042     }
1043
1044   es_free (buf);
1045   return err;
1046 }
1047
1048
1049 /* The GETPREORDER command retrieves a record from the preorder table.
1050
1051    Sepa-Ref:   The key to lookup the rceord.
1052
1053    On success these items are returned:
1054
1055    Sepa-Ref:   The Sepa-Ref string
1056    XXX:        FIXME:
1057
1058  */
1059 static gpg_error_t
1060 cmd_getpreorder (conn_t conn, char *args)
1061 {
1062   gpg_error_t err;
1063   keyvalue_t dict = conn->dataitems;
1064   keyvalue_t kv;
1065   const char *s;
1066   char *buf = NULL;
1067
1068   (void)args;
1069
1070   s = keyvalue_get_string (dict, "Sepa-Ref");
1071   if (!*s)
1072     {
1073       set_error (MISSING_VALUE, "Key 'Sepa-Ref' not given");
1074       goto leave;
1075     }
1076
1077   err = preorder_get_record (&conn->dataitems);
1078
1079  leave:
1080   if (err)
1081     {
1082       write_err_line (err, conn->errdesc, conn->stream);
1083       write_data_line (keyvalue_find (conn->dataitems, "failure"),
1084                        conn->stream);
1085       write_data_line (keyvalue_find (conn->dataitems, "failure-mesg"),
1086                        conn->stream);
1087     }
1088   else
1089     {
1090       write_ok_line (conn->stream);
1091       for (kv = conn->dataitems; kv; kv = kv->next)
1092         if (kv->name[0] >= 'A' && kv->name[0] < 'Z')
1093           write_data_line (kv, conn->stream);
1094     }
1095
1096   es_free (buf);
1097   return err;
1098 }
1099
1100
1101 /* The LISTPREORDER command retrieves a record from the preorder table.
1102
1103    Refnn:      The reference suffix (-NN).  If this is not given all
1104                records are listed in reverse chronological order.
1105
1106    On success these items are returned:
1107
1108    Count:      Number of records
1109    D[n]:       A formatted line with the data.  N starts at 0.
1110
1111  */
1112 static gpg_error_t
1113 cmd_listpreorder (conn_t conn, char *args)
1114 {
1115   gpg_error_t err;
1116   char *buf = NULL;
1117   unsigned int n, count;
1118   char key[30];
1119
1120   (void)args;
1121
1122   err = preorder_list_records (&conn->dataitems, &count);
1123
1124   if (err)
1125     {
1126       write_err_line (err, conn->errdesc, conn->stream);
1127       write_data_line (keyvalue_find (conn->dataitems, "failure"),
1128                        conn->stream);
1129       write_data_line (keyvalue_find (conn->dataitems, "failure-mesg"),
1130                        conn->stream);
1131     }
1132   else
1133     {
1134       write_ok_line (conn->stream);
1135       snprintf (key, sizeof key, "%u", count);
1136       write_data_line_direct ("Count", key, conn->stream);
1137       for (n=0; n < count; n++)
1138         {
1139           snprintf (key, sizeof key, "D[%u]", n);
1140           write_data_line_direct (key,
1141                                   keyvalue_get_string (conn->dataitems, key),
1142                                   conn->stream);
1143         }
1144     }
1145
1146   es_free (buf);
1147   return err;
1148 }
1149
1150
1151 \f
1152 /* The CHECKAMOUNT command checks whether a given amount is within the
1153  * configured limits for payment.  It may eventually provide
1154  * additional options.  The following values are expected in the
1155  * dataitems:
1156  *
1157  * Amount:     The amount to check with optional decimal fraction.
1158  * Currency:   A 3 letter currency code (EUR, USD, GBP, JPY)
1159  * Recur:      Optional: A recurrence interval: 0 = not recurring,
1160  *             1 = yearly, 4 = quarterly, 12 = monthly.
1161  *
1162  * On success these items are returned:
1163  *
1164  * _amount:    The amount converted to an integer (i.e. 10.42 EUR -> 1042)
1165  * Amount:     The amount as above; but see also Recur.
1166  * Recur:      0 or the Recur value.  To cope with too small amounts,
1167  *             this and then also Amount may be changed from the request.
1168  * Limit:      If given, the maximum amount acceptable
1169  * Euro:       If returned, Amount converted to Euro.
1170  */
1171 static gpg_error_t
1172 cmd_checkamount (conn_t conn, char *args)
1173 {
1174   gpg_error_t err;
1175   keyvalue_t dict = conn->dataitems;
1176   keyvalue_t kv;
1177   const char *curr;
1178   const char *s;
1179   unsigned int cents;
1180   int decdigs;
1181   char amountbuf[AMOUNTBUF_SIZE];
1182   int recur;
1183
1184   (void)args;
1185
1186   /* Delete items, we want to set.  */
1187   keyvalue_del (conn->dataitems, "Limit");
1188
1189   /* Get Recurrence value or replace by default.  */
1190   s = keyvalue_get_string (dict, "Recur");
1191   if (!valid_recur_p (s, &recur))
1192     {
1193       set_error (MISSING_VALUE, "Invalid value for 'Recur'");
1194       goto leave;
1195     }
1196   err = keyvalue_putf (&conn->dataitems, "Recur", "%d", recur);
1197   dict = conn->dataitems;
1198   if (err)
1199     goto leave;
1200
1201   /* Get currency and amount.  */
1202   curr = keyvalue_get_string (dict, "Currency");
1203   if (!valid_currency_p (curr, &decdigs))
1204     {
1205       set_error (MISSING_VALUE, "Currency missing or not supported");
1206       goto leave;
1207     }
1208
1209   s = keyvalue_get_string (dict, "Amount");
1210   if (!*s || !(cents = convert_amount (s, decdigs)))
1211     {
1212       set_error (MISSING_VALUE, "Amount missing or invalid");
1213       goto leave;
1214     }
1215
1216   if (*convert_currency (amountbuf, sizeof amountbuf, curr, s))
1217     err = keyvalue_put (&conn->dataitems, "Euro", amountbuf);
1218   else
1219     err = 0;
1220   if (err)
1221     goto leave;
1222
1223   err = keyvalue_putf (&conn->dataitems, "_amount", "%u", cents);
1224   dict = conn->dataitems;
1225   if (err)
1226     goto leave;
1227
1228  leave:
1229   if (err)
1230     {
1231       write_err_line (err, conn->errdesc, conn->stream);
1232     }
1233   else
1234     {
1235       write_ok_line (conn->stream);
1236       write_data_line (keyvalue_find (conn->dataitems, "_amount"),
1237                        conn->stream);
1238     }
1239   for (kv = conn->dataitems; kv; kv = kv->next)
1240     if (kv->name[0] >= 'A' && kv->name[0] < 'Z')
1241       write_data_line (kv, conn->stream);
1242   return err;
1243 }
1244
1245
1246 \f
1247 /* PPIPNHD is a handler for PayPal notifications.
1248
1249    Note: This is an asynchronous call: We send okay, *close* the
1250    socket, and only then process the IPN.  */
1251 static gpg_error_t
1252 cmd_ppipnhd (conn_t conn, char *args)
1253 {
1254   (void)args;
1255
1256   es_fputs ("OK\n\n", conn->stream);
1257   shutdown_connection_obj (conn);
1258   paypal_proc_ipn (&conn->dataitems);
1259   return 0;
1260 }
1261
1262
1263 \f
1264 /* GETINFO is a multipurpose command to return certain config data. It
1265    requires a subcommand.  See the online help for a list of
1266    subcommands.
1267  */
1268 static gpg_error_t
1269 cmd_getinfo (conn_t conn, char *args)
1270 {
1271   int i;
1272
1273   if (has_leading_keyword (args, "list-currencies"))
1274     {
1275       const char *name, *desc;
1276       double rate;
1277
1278       write_ok_line (conn->stream);
1279       for (i=0; (name = get_currency_info (i, &desc, &rate)); i++)
1280         write_rem_linef (conn->stream, "%s %11.4f - %s",
1281                          name, rate, desc);
1282     }
1283   else if (has_leading_keyword (args, "version"))
1284     {
1285       write_ok_linef (conn->stream, "%s", PACKAGE_VERSION);
1286     }
1287   else if (has_leading_keyword (args, "pid"))
1288     {
1289       write_ok_linef (conn->stream, "%u", (unsigned int)getpid());
1290     }
1291   else if (has_leading_keyword (args, "live"))
1292     {
1293       if (opt.livemode)
1294         write_ok_line (conn->stream);
1295       else
1296         write_err_line (179, "running in test mode", conn->stream);
1297     }
1298   else
1299     {
1300       write_err_line (1, "Unknown sub-command", conn->stream);
1301       write_rem_line ("Supported sub-commands are:", conn->stream);
1302       write_rem_line ("  list-currencies    List supported currencies",
1303                       conn->stream);
1304       write_rem_line ("  version            Show the version of this daemon",
1305                       conn->stream);
1306       write_rem_line ("  pid                Show the pid of this process",
1307                       conn->stream);
1308       write_rem_line ("  live               Returns OK if in live mode",
1309                       conn->stream);
1310     }
1311
1312   return 0;
1313 }
1314
1315
1316 /* Process a PING command.  */
1317 static gpg_error_t
1318 cmd_ping (conn_t conn, char *args)
1319 {
1320   write_ok_linef (conn->stream, "%s", *args? args : "pong");
1321   return 0;
1322 }
1323
1324
1325 /* Process a SHUTDOWN command.  */
1326 static gpg_error_t
1327 cmd_shutdown (conn_t conn, char *args)
1328 {
1329   (void)args;
1330
1331   write_ok_linef (conn->stream, "terminating daemon");
1332   shutdown_server ();
1333
1334   return 0;
1335 }
1336
1337
1338 \f
1339 static gpg_error_t cmd_help (conn_t conn, char *args);
1340
1341 /* The table with all commands. */
1342 static struct
1343 {
1344   const char *name;
1345   gpg_error_t (*handler)(conn_t conn, char *args);
1346   int admin_required;
1347 } cmdtbl[] =
1348   {
1349     { "SESSION",        cmd_session },
1350     { "CARDTOKEN",      cmd_cardtoken },
1351     { "CHARGECARD",     cmd_chargecard },
1352     { "PPCHECKOUT",     cmd_ppcheckout },
1353     { "SEPAPREORDER",   cmd_sepapreorder },
1354     { "CHECKAMOUNT",    cmd_checkamount },
1355     { "PPIPNHD",        cmd_ppipnhd },
1356     { "GETINFO",        cmd_getinfo },
1357     { "PING",           cmd_ping },
1358     { "COMMITPREORDER", cmd_commitpreorder, 1 },
1359     { "GETPREORDER",    cmd_getpreorder, 1 },
1360     { "LISTPREORDER",   cmd_listpreorder, 1 },
1361     { "SHUTDOWN",       cmd_shutdown, 1 },
1362     { "HELP",           cmd_help },
1363     { NULL, NULL}
1364   };
1365
1366
1367 /* The HELP command lists all commands.  */
1368 static gpg_error_t
1369 cmd_help (conn_t conn, char *args)
1370 {
1371   int cmdidx;
1372
1373   (void)args;
1374
1375   write_ok_line (conn->stream);
1376   for (cmdidx=0; cmdtbl[cmdidx].name; cmdidx++)
1377     write_rem_line (cmdtbl[cmdidx].name, conn->stream);
1378
1379   return 0;
1380 }
1381
1382
1383 /* The handler serving a connection.  UID is the UID of the client. */
1384 void
1385 connection_handler (conn_t conn, uid_t uid)
1386 {
1387   gpg_error_t err;
1388   keyvalue_t kv;
1389   int cmdidx;
1390   char *cmdargs;
1391   int i;
1392
1393   conn->stream = es_fdopen_nc (conn->fd, "r+,samethread");
1394   if (!conn->stream)
1395     {
1396       err = gpg_error_from_syserror ();
1397       log_error ("failed to open fd %d as stream: %s\n",
1398                  conn->fd, gpg_strerror (err));
1399       return;
1400     }
1401
1402   err = protocol_read_request (conn->stream, &conn->command, &conn->dataitems);
1403   if (err)
1404     {
1405       log_error ("reading request failed: %s\n", gpg_strerror (err));
1406       write_err_line (err, NULL, conn->stream);
1407       return;
1408     }
1409   es_fflush (conn->stream);
1410
1411   err = 0;
1412   if (opt.n_allowed_uids)
1413     {
1414       for (i=0; i < opt.n_allowed_uids; i++)
1415         if (opt.allowed_uids[i] == uid)
1416           break;
1417       if (!(i < opt.n_allowed_uids))
1418         {
1419           err = gpg_error (GPG_ERR_EPERM);
1420           write_err_line (err, "User not allowed", conn->stream);
1421         }
1422     }
1423
1424   if (!err)
1425     {
1426       cmdargs = NULL;
1427       for (cmdidx=0; cmdtbl[cmdidx].name; cmdidx++)
1428         if ((cmdargs=has_leading_keyword (conn->command, cmdtbl[cmdidx].name)))
1429           break;
1430       if (cmdargs)
1431         {
1432           err = 0;
1433           if (cmdtbl[cmdidx].admin_required)
1434             {
1435               for (i=0; i < opt.n_allowed_admin_uids; i++)
1436                 if (opt.allowed_admin_uids[i] == uid)
1437                   break;
1438               if (!(i < opt.n_allowed_admin_uids))
1439                 {
1440                   err = gpg_error (GPG_ERR_FORBIDDEN);
1441                   write_err_line (err, "User is not an admin", conn->stream);
1442                 }
1443             }
1444
1445           if (!err)
1446             {
1447               if (opt.debug_client)
1448                 {
1449                   log_debug ("client-req: %s\n", conn->command);
1450                   for (kv = conn->dataitems; kv; kv = kv->next)
1451                     log_debug ("client-req: %s: %s\n", kv->name, kv->value);
1452                   log_debug ("client-req: \n");
1453                 }
1454               err = cmdtbl[cmdidx].handler (conn, cmdargs);
1455
1456             }
1457         }
1458       else
1459         {
1460           write_err_line (1, "Unknown command", conn->stream);
1461           write_data_line_direct ("_cmd", conn->command? conn->command :"",
1462                                   conn->stream);
1463           for (kv = conn->dataitems; kv; kv = kv->next)
1464             write_data_line_direct (kv->name, kv->value? kv->value:"",
1465                                     conn->stream);
1466         }
1467     }
1468
1469   if (conn->stream)
1470     es_fprintf (conn->stream, "\n");
1471 }