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