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