181ac2fc9184356b326ce1dc82ff739a0f9685c5
[cascardo/libreceita.git] / rnetclient.c
1 /*
2  *  Copyright (C) 2012-2014  Thadeu Lima de Souza Cascardo <cascardo@minaslivre.org>
3  *  Copyright (C) 2014  Alexandre Oliva <lxoliva@fsfla.org>
4  *  Copyright (C) 2014  Sergio Durigan Junior <sergiodj@sergiodj.net>
5  *
6  *  This program 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  *  This program 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 along
17  *  with this program; if not, write to the Free Software Foundation, Inc.,
18  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include <string.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <errno.h>
25 #include <unistd.h>
26 #include <sys/socket.h>
27 #include <netinet/in.h>
28 #include <arpa/inet.h>
29 #include <netdb.h>
30 #include <gnutls/gnutls.h>
31 #include <zlib.h>
32 #include <argp.h>
33 #include "config.h"
34 #include "decfile.h"
35 #include "rnet_message.h"
36 #include "rnet_encode.h"
37
38 /* Program version and bug report address.  */
39
40 const char *argp_program_version = PACKAGE_VERSION;
41 const char *argp_program_bug_address = PACKAGE_BUGREPORT;
42
43 /* Documentation strings.  */
44
45 static const char rnetclient_doc[] =
46         "Send the Brazilian Income Tax Report to the Brazilian "
47         "Tax Authority";
48 static const char rnetclient_args_doc[] =
49         "[-d|--declaration] FILE";
50
51 /* Description and definition of each option accepted by the program.  */
52
53 static const struct argp_option rnetclient_options_desc[] = {
54         { "declaration", 'd', "FILE", 0,
55           "The Income Tax Report file that will be sent.",
56           0 },
57
58         { NULL },
59 };
60
61 struct rnetclient_args {
62         /* File representing the declaration.  */
63         char *input_file;
64 };
65
66 /* Parser for command line arguments.  */
67
68 static error_t rnetclient_parse_opt(int key, char *arg, struct argp_state *state)
69 {
70         struct rnetclient_args *a = state->input;
71         switch (key) {
72         case 'd':
73                 /* The user has explicitly provided a filename through
74                    the '-d' switch.  */
75                 a->input_file = arg;
76                 break;
77
78         case ARGP_KEY_ARG:
79                 /* The user has possibly provided a filename without
80                    using any switches (e.g., by running './rnetclient
81                    file').  */
82                 a->input_file = arg;
83                 break;
84
85         case ARGP_KEY_END:
86                 /* We have reached the end of the argument parsing.
87                    Let's check if the user has provided a filename.  */
88                 if (a->input_file == NULL)
89                         argp_error(state,
90                                    "You need to provide the Income Tax Declaration "
91                                    "filename.");
92         }
93
94         return 0;
95 }
96
97 /* Control struct used by argp.  */
98
99 static struct argp rnetclient_argp = {
100         rnetclient_options_desc,
101         rnetclient_parse_opt,
102         rnetclient_args_doc,
103         rnetclient_doc,
104         NULL, NULL, NULL
105 };
106
107 static size_t chars2len (unsigned char buf[2]) {
108         return (buf[0] << 8 | buf[1]);
109 }
110
111 static void * get_creds(char *certfile)
112 {
113         static gnutls_certificate_credentials_t cred;
114         gnutls_certificate_allocate_credentials(&cred);
115         gnutls_certificate_set_x509_trust_file(cred, certfile,
116                                         GNUTLS_X509_FMT_PEM);
117         return cred;
118 }
119
120 static void session_new(gnutls_session_t *session)
121 {
122         static void *cred;
123         cred = get_creds("cert.pem");
124         gnutls_init(session, GNUTLS_CLIENT);
125         gnutls_set_default_priority(*session);
126         gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, cred);
127 }
128
129 static int deflateRecord(char *buffer, size_t len, char **out, size_t *olen, int header)
130 {
131         z_stream zstrm;
132         int r;
133         zstrm.zalloc = Z_NULL;
134         zstrm.zfree = Z_NULL;
135         zstrm.opaque = Z_NULL;
136         if ((r = deflateInit(&zstrm, Z_DEFAULT_COMPRESSION)) != Z_OK)
137                 return -1;
138         *out = malloc(len * 2 + 36);
139         if (!out) {
140                 deflateEnd(&zstrm);
141                 return -1;
142         }
143         zstrm.next_in = buffer;
144         zstrm.avail_in = len;
145         zstrm.next_out = *out + 6;
146         zstrm.avail_out = len * 2 + 30;
147         while ((r = deflate(&zstrm, Z_FINISH)) != Z_STREAM_END &&
148                 zstrm.avail_out > 0);
149         if ((r = deflate(&zstrm, Z_FINISH)) != Z_STREAM_END) {
150                 deflateEnd(&zstrm);
151                 free(*out);
152                 return -1;
153         }
154         *olen = zstrm.total_out + 6;
155         (*out)[0] = 0x1;
156         (*out)[1] = (zstrm.total_out >> 8);
157         (*out)[2] = (zstrm.total_out & 0xff);
158         (*out)[3] = (len >> 8);
159         (*out)[4] = (len & 0xff);
160         (*out)[5] = header ? 0x01 : 0x0;
161         deflateEnd(&zstrm);
162         return 0;
163 }
164
165 static int inflateRecord(char *buffer, size_t len, char **out, size_t *olen)
166 {
167         z_stream zstrm;
168         int r;
169         zstrm.zalloc = Z_NULL;
170         zstrm.zfree = Z_NULL;
171         zstrm.opaque = Z_NULL;
172         if ((r = inflateInit(&zstrm)) != Z_OK)
173                 return -1;
174         *olen = chars2len(buffer+3);
175         *out = malloc(*olen);
176         if (!out) {
177                 inflateEnd(&zstrm);
178                 return -1;
179         }
180         zstrm.next_in = buffer + 6;
181         zstrm.avail_in = len - 6;
182         zstrm.next_out = *out;
183         zstrm.avail_out = *olen;
184         while ((r = inflate(&zstrm, Z_FINISH)) != Z_STREAM_END &&
185                 zstrm.avail_out > 0);
186         if ((r = inflate(&zstrm, Z_FINISH)) != Z_STREAM_END) {
187                 inflateEnd(&zstrm);
188                 free(*out);
189                 return -1;
190         }
191         inflateEnd(&zstrm);
192         return 0;
193 }
194
195 #define RNET_ADDRESS "receitanet.receita.fazenda.gov.br"
196
197 static int connect_rnet(int *c)
198 {
199         struct addrinfo *addresses;
200         struct addrinfo *addr;
201         struct addrinfo hint;
202         struct sockaddr_in saddr;
203         int r;
204         int fd = *c = -1;
205         int i;
206         memset(&hint, 0, sizeof(hint));
207         hint.ai_family = AF_UNSPEC;
208         hint.ai_socktype = SOCK_STREAM;
209         hint.ai_protocol = IPPROTO_TCP;
210         hint.ai_flags = AI_ADDRCONFIG;
211         r = getaddrinfo(RNET_ADDRESS, "3456", &hint, &addresses);
212         if (r) {
213                 return r;
214         }
215         for (addr = addresses; addr != NULL; addr = addr->ai_next) {
216                 fd = socket(addr->ai_family, addr->ai_socktype,
217                                 addr->ai_protocol);
218                 if (fd >= 0)
219                         if (!(r = connect(fd, addr->ai_addr,
220                                                 addr->ai_addrlen)))
221                                 break;
222                 close(fd);
223                 fd = -1;
224         }
225         freeaddrinfo(addresses);
226         *c = fd;
227         if (fd == -1)
228                 return EAI_SYSTEM;
229         return 0;
230 }
231
232 static int handshake(int c)
233 {
234         char buffer[16];
235         int r;
236         buffer[0] = 1;
237         r = write(c, buffer, 1);
238         if (r < 1)
239                 return -1;
240         r = write(c, "00000000000000", 14);
241         if (r < 14)
242                 return -1;
243         r = read(c, buffer, 1);
244         if (r != 1 && buffer[0] != 'E')
245                 return -1;
246         r = read(c, buffer, 14);
247         if (r != 14)
248                 return -1;
249         return 0;
250 }
251
252 static int rnet_send(gnutls_session_t session, char *buffer, size_t len, int header)
253 {
254         int r = 0;
255         /* Large files have to be uploaded as multiple
256            separately-deflated chunks, because the compressed and
257            uncompressed lengths in each record are encoded in unsigned
258            16-bit integers each.
259
260            The header can't be split into multiple chunks, and it
261            should never have to, since it won't ever get even close to
262            64KiB.
263
264            The uploaded file may be larger: to upload such large
265            files, it suffices to send multiple records till the entire
266            file is transferred, without waiting for a response.  Since
267            we've alread informed the server of the file size in the
268            header, it knows exactly how much data to expect before
269            sending a response.  It will only send an error message
270            before that if it times us out.
271
272            Odds are that any reasonably large size will do, but it
273            can't be too close to 64KiB, otherwise there won't be room
274            for the compressed length should it not compress well,
275            which should never happen for capital-ASCII-only
276            declaration files, but who knows?
277
278            This chunk size worked at the first try, uploading a
279            ~100KiB file, so let's stick with it.  */
280         const int maxc = 64472;
281         if (header && len > maxc)
282                 return -1;
283
284         do {
285                 char *out = NULL;
286                 size_t olen;
287                 size_t clen = len < maxc ? len : maxc;
288                 r = deflateRecord(buffer, clen, &out, &olen, header);
289                 if (!r) {
290                         size_t n = gnutls_record_send(session, out, olen);
291                         if (n != olen)
292                                 r = -1;
293                 }
294                 free(out);
295                 buffer += clen;
296                 len -= clen;
297         } while (len && !r);
298         return r;
299 }
300
301 static int rnet_recv(gnutls_session_t session, struct rnet_message **message)
302 {
303         char *out;
304         size_t olen;
305         int r;
306         char *buffer;
307         size_t len;
308         rnet_message_expand(message, 6);
309         buffer = (*message)->buffer;
310         r = gnutls_record_recv(session, buffer, 6);
311         if (buffer[0] == 0x01) {
312                 len = chars2len(buffer+1);
313                 rnet_message_expand(message, len);
314                 buffer = (*message)->buffer + 6;
315                 r = gnutls_record_recv(session, buffer, len);
316                 inflateRecord(buffer - 6, len + 6, &out, &olen);
317                 rnet_message_del(*message);
318                 *message = NULL;
319                 rnet_message_expand(message, olen);
320                 memcpy((*message)->buffer, out, olen);
321                 (*message)->len = olen;
322                 free(out);
323         } else {
324                 len = chars2len(buffer+1);
325                 rnet_message_expand(message, len - 1);
326                 buffer = (*message)->buffer + 6;
327                 r = gnutls_record_recv(session, buffer, len - 1);
328                 (*message)->len = len + 4;
329                 rnet_message_strip(*message, 4);
330         }
331         return 0;
332 }
333
334 static void save_rec_file(char *cpf, char *buffer, int len)
335 {
336         int fd;
337         char *filename;
338         char *home, *tmpdir;
339         mode_t mask;
340         size_t fnlen;
341         int r;
342         home = getenv("HOME");
343         if (!home) {
344                 tmpdir = getenv("TMPDIR");
345                 if (!tmpdir)
346                         tmpdir = "/tmp";
347                 home = tmpdir;
348         }
349         fnlen = strlen(home) + strlen(cpf) + 13;
350         filename = malloc(fnlen);
351         snprintf(filename, fnlen, "%s/%s.REC.XXXXXX", home, cpf);
352         mask = umask(0177);
353         fd = mkstemp(filename);
354         if (fd < 0) {
355                 fprintf(stderr, "Could not create receipt file: %s\n",
356                                                 strerror(errno));
357                 goto out;
358         }
359         r = write(fd, buffer, len);
360         if (r != len) {
361                 fprintf(stderr, "Could not write to receipt file%s%s\n",
362                         r < 0 ? ": " : ".",
363                         r < 0 ? strerror(errno) : "");
364                 goto out;
365         }
366         fprintf(stderr, "Wrote the receipt to %s.\n", filename);
367 out:
368         close(fd);
369         free(filename);
370         umask(mask);
371 }
372
373 static void handle_response_text_and_file(char *cpf, struct rnet_message *message)
374 {
375         char *value;
376         int vlen;
377         if (!rnet_message_parse(message, "texto", &value, &vlen))
378                 fprintf(stderr, "%.*s\n", vlen, value);
379         if (!rnet_message_parse(message, "arquivo", &value, &vlen))
380                 save_rec_file(cpf, value, vlen);
381 }
382
383 static void handle_response_already_found(char *cpf, struct rnet_message *message)
384 {
385         handle_response_text_and_file(cpf, message);
386 }
387
388 static void handle_response_error(struct rnet_message *message)
389 {
390         char *value;
391         int vlen;
392         if (!rnet_message_parse(message, "texto", &value, &vlen))
393                 fprintf(stderr, "%.*s\n", vlen, value);
394         fprintf(stderr, "Error transmiting DEC file.\n");
395 }
396
397 int main(int argc, char **argv)
398 {
399         int c;
400         int r;
401         struct rnet_decfile *decfile;
402         struct rnet_message *message = NULL;
403         struct rnetclient_args rnet_args;
404         gnutls_session_t session;
405         int finish = 0;
406         char *cpf;
407         error_t err;
408
409         /* Parsing the command line arguments.  The argp_parse
410            function calls exit() if there is some error during the
411            parsing process (e.g., the user has provided an unknown
412            flag or the parsing function has called argp_error).
413            However, if our internal parsing function returns something
414            different than zero, then argp_parse returns this value to
415            us.  This is a bug, and should not happen in the current
416            state.  */
417         memset(&rnet_args, 0, sizeof (rnet_args));
418         err = argp_parse (&rnetclient_argp, argc, argv, 0, NULL, &rnet_args);
419         if (err != 0)
420                 fprintf(stderr, "internal error while parsing command line arguments.");
421
422         decfile = rnet_decfile_open(rnet_args.input_file);
423         if (!decfile) {
424                 fprintf(stderr, "could not parse file \"%s\": %s\n", rnet_args.input_file, strerror(errno));
425                 exit(1);
426         }
427
428         cpf = rnet_decfile_get_header_field(decfile, "cpf");
429
430         gnutls_global_init();
431
432         session_new(&session);
433         r = connect_rnet(&c);
434         if (r) {
435                 fprintf(stderr, "error connecting to server: %s\n",
436                         r == EAI_SYSTEM ? strerror(errno) : gai_strerror(r));
437                 exit(1);
438         }
439         gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)(intptr_t) c);
440         r = handshake(c);
441         if (r < 0) {
442                 exit(1);
443         }
444         if ((r = gnutls_handshake(session)) < 0)
445                 fprintf(stderr, "error in handshake: %s\n",
446                                 gnutls_strerror(r));
447
448         rnet_encode(decfile, &message);
449         rnet_send(session, message->buffer, message->len, 1);
450         rnet_message_del(message);
451
452         message = NULL;
453         r = rnet_recv(session, &message);
454         if (r || !message || message->len == 0) {
455                 fprintf(stderr, "error when receiving response\n");
456                 goto out;
457         }
458         switch (message->buffer[0]) {
459         case 1: /* go ahead */
460                 handle_response_text_and_file(cpf, message);
461                 break;
462         case 3: /* error */
463                 handle_response_error(message);
464                 finish = 1;
465                 break;
466         case 4:
467                 handle_response_already_found(cpf, message);
468                 finish = 1;
469                 break;
470         case 2:
471         case 5:
472                 handle_response_text_and_file(cpf, message);
473                 finish = 1;
474                 break;
475         }
476         rnet_message_del(message);
477
478         if (finish)
479                 goto out;
480
481         message = rnet_decfile_get_file(decfile);
482         rnet_send(session, message->buffer, message->len, 0);
483
484         message = NULL;
485         r = rnet_recv(session, &message);
486         if (r || !message || message->len == 0) {
487                 fprintf(stderr, "error when receiving response\n");
488                 goto out;
489         }
490         switch (message->buffer[0]) {
491         case 3: /* error */
492                 handle_response_error(message);
493                 break;
494         case 2:
495         case 4:
496         case 5:
497         case 1:
498                 handle_response_text_and_file(cpf, message);
499                 break;
500         }
501         
502 out:
503         gnutls_bye(session, GNUTLS_SHUT_RDWR);
504         close(c);
505         rnet_decfile_close(decfile);
506         gnutls_global_deinit();
507
508         return 0;
509 }