1 /* client.c - Functions common to all clients.
2  * Copyright (C) 2009 Free Software Foundation, Inc.
3  *
4  * This file is part of Assuan.
5  *
6  * Assuan is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * Assuan is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
18  * SPDX-License-Identifier: LGPL-2.1+
19  */
20 
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include <stdlib.h>
27 
28 #include "assuan-defs.h"
29 #include "debug.h"
30 
31 #define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
32                      *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
33 #define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
34 
35 
36 void
_assuan_client_finish(assuan_context_t ctx)37 _assuan_client_finish (assuan_context_t ctx)
38 {
39   if (ctx->inbound.fd != ASSUAN_INVALID_FD)
40     {
41       _assuan_close (ctx, ctx->inbound.fd);
42       if (ctx->inbound.fd == ctx->outbound.fd)
43         ctx->outbound.fd = ASSUAN_INVALID_FD;
44       ctx->inbound.fd = ASSUAN_INVALID_FD;
45     }
46   if (ctx->outbound.fd != ASSUAN_INVALID_FD)
47     {
48       _assuan_close (ctx, ctx->outbound.fd);
49       ctx->outbound.fd = ASSUAN_INVALID_FD;
50     }
51   if (ctx->pid != ASSUAN_INVALID_PID && ctx->pid)
52     {
53       _assuan_waitpid (ctx, ctx->pid, ctx->flags.no_waitpid, NULL, 0);
54       ctx->pid = ASSUAN_INVALID_PID;
55     }
56 
57   _assuan_uds_deinit (ctx);
58 }
59 
60 
61 /* Disconnect and release the context CTX.  */
62 void
_assuan_client_release(assuan_context_t ctx)63 _assuan_client_release (assuan_context_t ctx)
64 {
65   assuan_write_line (ctx, "BYE");
66 
67   _assuan_client_finish (ctx);
68 }
69 
70 
71 /* This function also does deescaping for data lines.  */
72 gpg_error_t
assuan_client_read_response(assuan_context_t ctx,char ** line_r,int * linelen_r)73 assuan_client_read_response (assuan_context_t ctx,
74 			     char **line_r, int *linelen_r)
75 {
76   gpg_error_t rc;
77   char *line = NULL;
78   int linelen = 0;
79 
80   *line_r = NULL;
81   *linelen_r = 0;
82 
83   do
84     {
85       do
86 	{
87 	  rc = _assuan_read_line (ctx);
88 	}
89       while (_assuan_error_is_eagain (ctx, rc));
90       if (rc)
91         return rc;
92       line = ctx->inbound.line;
93       linelen = ctx->inbound.linelen;
94     }
95   while (!linelen);
96 
97   /* For data lines, we deescape immediately.  The user will never
98      have to worry about it.  */
99   if (linelen >= 1 && line[0] == 'D' && line[1] == ' ')
100     {
101       char *s, *d;
102       for (s=d=line; linelen; linelen--)
103 	{
104 	  if (*s == '%' && linelen > 2)
105 	    { /* handle escaping */
106 	      s++;
107 	      *d++ = xtoi_2 (s);
108 	      s += 2;
109 	      linelen -= 2;
110 	    }
111 	  else
112 	    *d++ = *s++;
113 	}
114       *d = 0; /* add a hidden string terminator */
115 
116       linelen = d - line;
117       ctx->inbound.linelen = linelen;
118     }
119 
120   *line_r = line;
121   *linelen_r = linelen;
122 
123   return 0;
124 }
125 
126 
127 gpg_error_t
assuan_client_parse_response(assuan_context_t ctx,char * line,int linelen,assuan_response_t * response,int * off)128 assuan_client_parse_response (assuan_context_t ctx, char *line, int linelen,
129 			      assuan_response_t *response, int *off)
130 {
131   *response = ASSUAN_RESPONSE_ERROR;
132   *off = 0;
133 
134   if (linelen >= 1
135       && line[0] == 'D' && line[1] == ' ')
136     {
137       *response = ASSUAN_RESPONSE_DATA; /* data line */
138       *off = 2;
139     }
140   else if (linelen >= 1
141            && line[0] == 'S'
142            && (line[1] == '\0' || line[1] == ' '))
143     {
144       *response = ASSUAN_RESPONSE_STATUS;
145       *off = 1;
146       while (line[*off] == ' ')
147         ++*off;
148     }
149   else if (linelen >= 2
150            && line[0] == 'O' && line[1] == 'K'
151            && (line[2] == '\0' || line[2] == ' '))
152     {
153       *response = ASSUAN_RESPONSE_OK;
154       *off = 2;
155       while (line[*off] == ' ')
156         ++*off;
157     }
158   else if (linelen >= 3
159            && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
160            && (line[3] == '\0' || line[3] == ' '))
161     {
162       *response = ASSUAN_RESPONSE_ERROR;
163       *off = 3;
164       while (line[*off] == ' ')
165         ++*off;
166     }
167   else if (linelen >= 7
168            && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
169            && line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
170            && line[6] == 'E'
171            && (line[7] == '\0' || line[7] == ' '))
172     {
173       *response = ASSUAN_RESPONSE_INQUIRE;
174       *off = 7;
175       while (line[*off] == ' ')
176         ++*off;
177     }
178   else if (linelen >= 3
179            && line[0] == 'E' && line[1] == 'N' && line[2] == 'D'
180            && (line[3] == '\0' || line[3] == ' '))
181     {
182       *response = ASSUAN_RESPONSE_END;
183       *off = 3;
184     }
185   else if (linelen >= 1 && line[0] == '#')
186     {
187       *response = ASSUAN_RESPONSE_COMMENT;
188       *off = 1;
189     }
190   else
191     return _assuan_error (ctx, GPG_ERR_ASS_INV_RESPONSE);
192 
193   return 0;
194 }
195 
196 
197 gpg_error_t
_assuan_read_from_server(assuan_context_t ctx,assuan_response_t * response,int * off,int convey_comments)198 _assuan_read_from_server (assuan_context_t ctx, assuan_response_t *response,
199 			  int *off, int convey_comments)
200 {
201   gpg_error_t rc;
202   char *line;
203   int linelen;
204 
205   do
206     {
207       *response = ASSUAN_RESPONSE_ERROR;
208       *off = 0;
209       rc = assuan_client_read_response (ctx, &line, &linelen);
210       if (!rc)
211         rc = assuan_client_parse_response (ctx, line, linelen, response, off);
212     }
213   while (!rc && *response == ASSUAN_RESPONSE_COMMENT && !convey_comments);
214 
215   return rc;
216 }
217 
218 
219 /**
220  * assuan_transact:
221  * @ctx: The Assuan context
222  * @command: Command line to be send to the server
223  * @data_cb: Callback function for data lines
224  * @data_cb_arg: first argument passed to @data_cb
225  * @inquire_cb: Callback function for a inquire response
226  * @inquire_cb_arg: first argument passed to @inquire_cb
227  * @status_cb: Callback function for a status response
228  * @status_cb_arg: first argument passed to @status_cb
229  *
230  * FIXME: Write documentation
231  *
232  * Return value: 0 on success or an error code.  The error code may be
233  * the one one returned by the server via error lines or from the
234  * callback functions.  Take care:  If a callback returns an error
235  * this function returns immediately with this error.
236  **/
237 gpg_error_t
assuan_transact(assuan_context_t ctx,const char * command,gpg_error_t (* data_cb)(void *,const void *,size_t),void * data_cb_arg,gpg_error_t (* inquire_cb)(void *,const char *),void * inquire_cb_arg,gpg_error_t (* status_cb)(void *,const char *),void * status_cb_arg)238 assuan_transact (assuan_context_t ctx,
239                  const char *command,
240                  gpg_error_t (*data_cb)(void *, const void *, size_t),
241                  void *data_cb_arg,
242                  gpg_error_t (*inquire_cb)(void*, const char *),
243                  void *inquire_cb_arg,
244                  gpg_error_t (*status_cb)(void*, const char *),
245                  void *status_cb_arg)
246 {
247   gpg_error_t rc;
248   assuan_response_t response;
249   int off;
250   char *line;
251   int linelen;
252 
253   rc = assuan_write_line (ctx, command);
254   if (rc)
255     return rc;
256 
257   if (*command == '#' || !*command)
258     return 0; /* Don't expect a response for a comment line.  */
259 
260  again:
261   rc = _assuan_read_from_server (ctx, &response, &off,
262                                  ctx->flags.convey_comments);
263   if (rc)
264     return rc; /* error reading from server */
265 
266   line = ctx->inbound.line + off;
267   linelen = ctx->inbound.linelen - off;
268 
269   if (response == ASSUAN_RESPONSE_ERROR)
270     rc = atoi (line);
271   else if (response == ASSUAN_RESPONSE_DATA)
272     {
273       if (!data_cb)
274         rc = _assuan_error (ctx, GPG_ERR_ASS_NO_DATA_CB);
275       else
276         {
277           rc = data_cb (data_cb_arg, line, linelen);
278           if (!rc)
279             goto again;
280         }
281     }
282   else if (response == ASSUAN_RESPONSE_INQUIRE)
283     {
284       if (!inquire_cb)
285         {
286           assuan_write_line (ctx, "END"); /* get out of inquire mode */
287           _assuan_read_from_server (ctx, &response, &off, 0); /* dummy read */
288           rc = _assuan_error (ctx, GPG_ERR_ASS_NO_INQUIRE_CB);
289         }
290       else
291         {
292           rc = inquire_cb (inquire_cb_arg, line);
293           if (!rc)
294             rc = assuan_send_data (ctx, NULL, 0); /* flush and send END */
295           else
296             { /* Flush and send CAN.  */
297               /* Note that in this error case we don't want to return
298                  an error code from sending the cancel.  The dummy
299                  read is to remove the response from the server which
300                  we are not interested in.  */
301               assuan_send_data (ctx, NULL, 1);
302               _assuan_read_from_server (ctx, &response, &off, 0);
303             }
304           if (!rc)
305             goto again;
306         }
307     }
308   else if (response == ASSUAN_RESPONSE_STATUS)
309     {
310       if (status_cb)
311         rc = status_cb (status_cb_arg, line);
312       if (!rc)
313         goto again;
314     }
315   else if (response == ASSUAN_RESPONSE_COMMENT && ctx->flags.convey_comments)
316     {
317       line -= off; /* Send line with the comment marker.  */
318       if (status_cb)
319         rc = status_cb (status_cb_arg, line);
320       if (!rc)
321         goto again;
322     }
323   else if (response == ASSUAN_RESPONSE_END)
324     {
325       if (!data_cb)
326         rc = _assuan_error (ctx, GPG_ERR_ASS_NO_DATA_CB);
327       else
328         {
329           rc = data_cb (data_cb_arg, NULL, 0);
330           if (!rc)
331             goto again;
332         }
333     }
334 
335   return rc;
336 }
337