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>
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.
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.
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.
26 #include <sys/socket.h>
27 #include <netinet/in.h>
28 #include <arpa/inet.h>
30 #include <gnutls/gnutls.h>
35 #include "rnet_message.h"
36 #include "rnet_encode.h"
38 /* Program version and bug report address. */
40 const char *argp_program_version = PACKAGE_VERSION;
41 const char *argp_program_bug_address = PACKAGE_BUGREPORT;
43 /* Documentation strings. */
45 static const char rnetclient_doc[] =
46 "Send the Brazilian Income Tax Report to the Brazilian "
48 static const char rnetclient_args_doc[] =
49 "[-d|--declaration] FILE";
51 /* Description and definition of each option accepted by the program. */
53 static const struct argp_option rnetclient_options_desc[] = {
54 { "declaration", 'd', "FILE", 0,
55 "The Income Tax Report file that will be sent.",
61 struct rnetclient_args {
62 /* File representing the declaration. */
66 /* Parser for command line arguments. */
68 static error_t rnetclient_parse_opt(int key, char *arg, struct argp_state *state)
70 struct rnetclient_args *a = state->input;
73 /* The user has explicitly provided a filename through
79 /* The user has possibly provided a filename without
80 using any switches (e.g., by running './rnetclient
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)
90 "You need to provide the Income Tax Declaration "
97 /* Control struct used by argp. */
99 static struct argp rnetclient_argp = {
100 rnetclient_options_desc,
101 rnetclient_parse_opt,
107 static size_t chars2len (unsigned char buf[2]) {
108 return (buf[0] << 8 | buf[1]);
111 static void * get_creds(char *certfile)
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);
120 static void session_new(gnutls_session_t *session)
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);
129 static int deflateRecord(char *buffer, size_t len, char **out, size_t *olen, int header)
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)
138 *out = malloc(len * 2 + 36);
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) {
154 *olen = zstrm.total_out + 6;
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;
165 static int inflateRecord(char *buffer, size_t len, char **out, size_t *olen)
169 zstrm.zalloc = Z_NULL;
170 zstrm.zfree = Z_NULL;
171 zstrm.opaque = Z_NULL;
172 if ((r = inflateInit(&zstrm)) != Z_OK)
174 *olen = chars2len(buffer+3);
175 *out = malloc(*olen);
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) {
195 #define RNET_ADDRESS "receitanet.receita.fazenda.gov.br"
197 static int connect_rnet(int *c)
199 struct addrinfo *addresses;
200 struct addrinfo *addr;
201 struct addrinfo hint;
202 struct sockaddr_in saddr;
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);
215 for (addr = addresses; addr != NULL; addr = addr->ai_next) {
216 fd = socket(addr->ai_family, addr->ai_socktype,
219 if (!(r = connect(fd, addr->ai_addr,
225 freeaddrinfo(addresses);
232 static int handshake(int c)
237 r = write(c, buffer, 1);
240 r = write(c, "00000000000000", 14);
243 r = read(c, buffer, 1);
244 if (r != 1 && buffer[0] != 'E')
246 r = read(c, buffer, 14);
252 static int rnet_send(gnutls_session_t session, char *buffer, size_t len, int header)
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.
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
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.
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?
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)
287 size_t clen = len < maxc ? len : maxc;
288 r = deflateRecord(buffer, clen, &out, &olen, header);
290 size_t n = gnutls_record_send(session, out, olen);
301 static int rnet_recv(gnutls_session_t session, struct rnet_message **message)
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);
319 rnet_message_expand(message, olen);
320 memcpy((*message)->buffer, out, olen);
321 (*message)->len = olen;
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);
334 static void save_rec_file(char *cpf, char *buffer, int len)
342 home = getenv("HOME");
344 tmpdir = getenv("TMPDIR");
349 fnlen = strlen(home) + strlen(cpf) + 13;
350 filename = malloc(fnlen);
351 snprintf(filename, fnlen, "%s/%s.REC.XXXXXX", home, cpf);
353 fd = mkstemp(filename);
355 fprintf(stderr, "Could not create receipt file: %s\n",
359 r = write(fd, buffer, len);
361 fprintf(stderr, "Could not write to receipt file%s%s\n",
363 r < 0 ? strerror(errno) : "");
366 fprintf(stderr, "Wrote the receipt to %s.\n", filename);
373 static void handle_response_text_and_file(char *cpf, struct rnet_message *message)
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);
383 static void handle_response_already_found(char *cpf, struct rnet_message *message)
385 handle_response_text_and_file(cpf, message);
388 static void handle_response_error(struct rnet_message *message)
392 if (!rnet_message_parse(message, "texto", &value, &vlen))
393 fprintf(stderr, "%.*s\n", vlen, value);
394 fprintf(stderr, "Error transmiting DEC file.\n");
397 int main(int argc, char **argv)
401 struct rnet_decfile *decfile;
402 struct rnet_message *message = NULL;
403 struct rnetclient_args rnet_args;
404 gnutls_session_t session;
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
417 memset(&rnet_args, 0, sizeof (rnet_args));
418 err = argp_parse (&rnetclient_argp, argc, argv, 0, NULL, &rnet_args);
420 fprintf(stderr, "internal error while parsing command line arguments.");
422 decfile = rnet_decfile_open(rnet_args.input_file);
424 fprintf(stderr, "could not parse file \"%s\": %s\n", rnet_args.input_file, strerror(errno));
428 cpf = rnet_decfile_get_header_field(decfile, "cpf");
430 gnutls_global_init();
432 session_new(&session);
433 r = connect_rnet(&c);
435 fprintf(stderr, "error connecting to server: %s\n",
436 r == EAI_SYSTEM ? strerror(errno) : gai_strerror(r));
439 gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)(intptr_t) c);
444 if ((r = gnutls_handshake(session)) < 0)
445 fprintf(stderr, "error in handshake: %s\n",
448 rnet_encode(decfile, &message);
449 rnet_send(session, message->buffer, message->len, 1);
450 rnet_message_del(message);
453 r = rnet_recv(session, &message);
454 if (r || !message || message->len == 0) {
455 fprintf(stderr, "error when receiving response\n");
458 switch (message->buffer[0]) {
459 case 1: /* go ahead */
460 handle_response_text_and_file(cpf, message);
463 handle_response_error(message);
467 handle_response_already_found(cpf, message);
472 handle_response_text_and_file(cpf, message);
476 rnet_message_del(message);
481 message = rnet_decfile_get_file(decfile);
482 rnet_send(session, message->buffer, message->len, 0);
485 r = rnet_recv(session, &message);
486 if (r || !message || message->len == 0) {
487 fprintf(stderr, "error when receiving response\n");
490 switch (message->buffer[0]) {
492 handle_response_error(message);
498 handle_response_text_and_file(cpf, message);
503 gnutls_bye(session, GNUTLS_SHUT_RDWR);
505 rnet_decfile_close(decfile);
506 gnutls_global_deinit();