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