d264c72817fefce9628dc1d60de193bbcdd67de5
[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 "decfile.h"
31 #include "rnet_message.h"
32 #include "rnet_encode.h"
33
34 static size_t chars2len (unsigned char buf[2]) {
35         return (buf[0] << 8 | buf[1]);
36 }
37
38 static void * get_creds(char *certfile)
39 {
40         static gnutls_certificate_credentials_t cred;
41         gnutls_certificate_allocate_credentials(&cred);
42         gnutls_certificate_set_x509_trust_file(cred, certfile,
43                                         GNUTLS_X509_FMT_PEM);
44         return cred;
45 }
46
47 static void session_new(gnutls_session_t *session)
48 {
49         static void *cred;
50         cred = get_creds("cert.pem");
51         gnutls_init(session, GNUTLS_CLIENT);
52         gnutls_set_default_priority(*session);
53         gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, cred);
54 }
55
56 static int deflateRecord(char *buffer, size_t len, char **out, size_t *olen, int header)
57 {
58         z_stream zstrm;
59         int r;
60         zstrm.zalloc = Z_NULL;
61         zstrm.zfree = Z_NULL;
62         zstrm.opaque = Z_NULL;
63         if ((r = deflateInit(&zstrm, Z_DEFAULT_COMPRESSION)) != Z_OK)
64                 return -1;
65         *out = malloc(len * 2 + 36);
66         if (!out) {
67                 deflateEnd(&zstrm);
68                 return -1;
69         }
70         zstrm.next_in = buffer;
71         zstrm.avail_in = len;
72         zstrm.next_out = *out + 6;
73         zstrm.avail_out = len * 2 + 30;
74         while ((r = deflate(&zstrm, Z_FINISH)) != Z_STREAM_END &&
75                 zstrm.avail_out > 0);
76         if ((r = deflate(&zstrm, Z_FINISH)) != Z_STREAM_END) {
77                 deflateEnd(&zstrm);
78                 free(*out);
79                 return -1;
80         }
81         *olen = zstrm.total_out + 6;
82         (*out)[0] = 0x1;
83         (*out)[1] = (zstrm.total_out >> 8);
84         (*out)[2] = (zstrm.total_out & 0xff);
85         (*out)[3] = (len >> 8);
86         (*out)[4] = (len & 0xff);
87         (*out)[5] = header ? 0x01 : 0x0;
88         deflateEnd(&zstrm);
89         return 0;
90 }
91
92 static int inflateRecord(char *buffer, size_t len, char **out, size_t *olen)
93 {
94         z_stream zstrm;
95         int r;
96         zstrm.zalloc = Z_NULL;
97         zstrm.zfree = Z_NULL;
98         zstrm.opaque = Z_NULL;
99         if ((r = inflateInit(&zstrm)) != Z_OK)
100                 return -1;
101         *olen = chars2len(buffer+3);
102         *out = malloc(*olen);
103         if (!out) {
104                 inflateEnd(&zstrm);
105                 return -1;
106         }
107         zstrm.next_in = buffer + 6;
108         zstrm.avail_in = len - 6;
109         zstrm.next_out = *out;
110         zstrm.avail_out = *olen;
111         while ((r = inflate(&zstrm, Z_FINISH)) != Z_STREAM_END &&
112                 zstrm.avail_out > 0);
113         if ((r = inflate(&zstrm, Z_FINISH)) != Z_STREAM_END) {
114                 inflateEnd(&zstrm);
115                 free(*out);
116                 return -1;
117         }
118         inflateEnd(&zstrm);
119         return 0;
120 }
121
122 #define RNET_ADDRESS "receitanet.receita.fazenda.gov.br"
123
124 static int connect_rnet(int *c)
125 {
126         struct addrinfo *addresses;
127         struct addrinfo *addr;
128         struct addrinfo hint;
129         struct sockaddr_in saddr;
130         int r;
131         int fd = *c = -1;
132         int i;
133         memset(&hint, 0, sizeof(hint));
134         hint.ai_family = AF_UNSPEC;
135         hint.ai_socktype = SOCK_STREAM;
136         hint.ai_protocol = IPPROTO_TCP;
137         hint.ai_flags = AI_ADDRCONFIG;
138         r = getaddrinfo(RNET_ADDRESS, "3456", &hint, &addresses);
139         if (r) {
140                 return r;
141         }
142         for (addr = addresses; addr != NULL; addr = addr->ai_next) {
143                 fd = socket(addr->ai_family, addr->ai_socktype,
144                                 addr->ai_protocol);
145                 if (fd >= 0)
146                         if (!(r = connect(fd, addr->ai_addr,
147                                                 addr->ai_addrlen)))
148                                 break;
149                 close(fd);
150                 fd = -1;
151         }
152         freeaddrinfo(addresses);
153         *c = fd;
154         if (fd == -1)
155                 return EAI_SYSTEM;
156         return 0;
157 }
158
159 static int handshake(int c)
160 {
161         char buffer[16];
162         int r;
163         buffer[0] = 1;
164         write(c, buffer, 1);
165         write(c, "00000000000000", 14);
166         r = read(c, buffer, 1);
167         if (r != 1 && buffer[0] != 'E')
168                 return -1;
169         r = read(c, buffer, 14);
170         if (r != 14)
171                 return -1;
172         return 0;
173 }
174
175 static void usage(void)
176 {
177         fprintf(stderr, "rnetclient [filename]\n");
178         exit(1);
179 }
180
181 static int rnet_send(gnutls_session_t session, char *buffer, size_t len, int header)
182 {
183         int r = 0;
184         /* Large files have to be uploaded as multiple
185            separately-deflated chunks, because the compressed and
186            uncompressed lengths in each record are encoded in unsigned
187            16-bit integers each.
188
189            The header can't be split into multiple chunks, and it
190            should never have to, since it won't ever get even close to
191            64KiB.
192
193            The uploaded file may be larger: to upload such large
194            files, it suffices to send multiple records till the entire
195            file is transferred, without waiting for a response.  Since
196            we've alread informed the server of the file size in the
197            header, it knows exactly how much data to expect before
198            sending a response.  It will only send an error message
199            before that if it times us out.
200
201            Odds are that any reasonably large size will do, but it
202            can't be too close to 64KiB, otherwise there won't be room
203            for the compressed length should it not compress well,
204            which should never happen for capital-ASCII-only
205            declaration files, but who knows?
206
207            This chunk size worked at the first try, uploading a
208            ~100KiB file, so let's stick with it.  */
209         const int maxc = 64472;
210         if (header && len > maxc)
211                 return -1;
212
213         do {
214                 char *out = NULL;
215                 size_t olen;
216                 size_t clen = len < maxc ? len : maxc;
217                 r = deflateRecord(buffer, clen, &out, &olen, header);
218                 if (!r) {
219                         size_t n = gnutls_record_send(session, out, olen);
220                         if (n != olen)
221                                 r = -1;
222                 }
223                 free(out);
224                 buffer += clen;
225                 len -= clen;
226         } while (len && !r);
227         return r;
228 }
229
230 static int rnet_recv(gnutls_session_t session, struct rnet_message **message)
231 {
232         char *out;
233         size_t olen;
234         int r;
235         char *buffer;
236         size_t len;
237         rnet_message_expand(message, 6);
238         buffer = (*message)->buffer;
239         r = gnutls_record_recv(session, buffer, 6);
240         if (buffer[0] == 0x01) {
241                 len = chars2len(buffer+1);
242                 rnet_message_expand(message, len);
243                 buffer = (*message)->buffer + 6;
244                 r = gnutls_record_recv(session, buffer, len);
245                 inflateRecord(buffer - 6, len + 6, &out, &olen);
246                 rnet_message_del(*message);
247                 *message = NULL;
248                 rnet_message_expand(message, olen);
249                 memcpy((*message)->buffer, out, olen);
250                 (*message)->len = olen;
251                 free(out);
252         } else {
253                 len = chars2len(buffer+1);
254                 rnet_message_expand(message, len - 1);
255                 buffer = (*message)->buffer + 6;
256                 r = gnutls_record_recv(session, buffer, len - 1);
257                 (*message)->len = len + 4;
258                 rnet_message_strip(*message, 4);
259         }
260         return 0;
261 }
262
263 static void save_rec_file(char *cpf, char *buffer, int len)
264 {
265         int fd;
266         char *filename;
267         char *home, *tmpdir;
268         mode_t mask;
269         size_t fnlen;
270         int r;
271         home = getenv("HOME");
272         if (!home) {
273                 tmpdir = getenv("TMPDIR");
274                 if (!tmpdir)
275                         tmpdir = "/tmp";
276                 home = tmpdir;
277         }
278         fnlen = strlen(home) + strlen(cpf) + 13;
279         filename = malloc(fnlen);
280         snprintf(filename, fnlen, "%s/%s.REC.XXXXXX", home, cpf);
281         mask = umask(0177);
282         fd = mkstemp(filename);
283         if (fd < 0) {
284                 fprintf(stderr, "Could not create receipt file: %s\n",
285                                                 strerror(errno));
286                 goto out;
287         }
288         r = write(fd, buffer, len);
289         if (r != len) {
290                 fprintf(stderr, "Could not write to receipt file%s%s\n",
291                         r < 0 ? ": " : ".",
292                         r < 0 ? strerror(errno) : "");
293                 goto out;
294         }
295         fprintf(stderr, "Wrote the receipt to %s.\n", filename);
296 out:
297         close(fd);
298         free(filename);
299         umask(mask);
300 }
301
302 static void handle_response_text_and_file(char *cpf, struct rnet_message *message)
303 {
304         char *value;
305         int vlen;
306         if (!rnet_message_parse(message, "texto", &value, &vlen))
307                 fprintf(stderr, "%.*s\n", vlen, value);
308         if (!rnet_message_parse(message, "arquivo", &value, &vlen))
309                 save_rec_file(cpf, value, vlen);
310 }
311
312 static void handle_response_already_found(char *cpf, struct rnet_message *message)
313 {
314         handle_response_text_and_file(cpf, message);
315 }
316
317 static void handle_response_error(struct rnet_message *message)
318 {
319         char *value;
320         int vlen;
321         if (!rnet_message_parse(message, "texto", &value, &vlen))
322                 fprintf(stderr, "%.*s\n", vlen, value);
323         fprintf(stderr, "Error transmiting DEC file.\n");
324 }
325
326 int main(int argc, char **argv)
327 {
328         int c;
329         int r;
330         struct rnet_decfile *decfile;
331         struct rnet_message *message = NULL;
332         gnutls_session_t session;
333         int finish = 0;
334         char *cpf;
335         
336         if (argc < 2) {
337                 usage();
338         }
339
340         decfile = rnet_decfile_open(argv[1]);
341         if (!decfile) {
342                 fprintf(stderr, "could not parse %s: %s\n", argv[1], strerror(errno));
343                 exit(1);
344         }
345
346         cpf = rnet_decfile_get_header_field(decfile, "cpf");
347
348         gnutls_global_init();
349
350         session_new(&session);
351         r = connect_rnet(&c);
352         if (r) {
353                 fprintf(stderr, "error connecting to server: %s\n",
354                         r == EAI_SYSTEM ? strerror(errno) : gai_strerror(r));
355                 exit(1);
356         }
357         gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)(intptr_t) c);
358         r = handshake(c);
359         if (r < 0) {
360                 exit(1);
361         }
362         if ((r = gnutls_handshake(session)) < 0)
363                 fprintf(stderr, "error in handshake: %s\n",
364                                 gnutls_strerror(r));
365
366         rnet_encode(decfile, &message);
367         rnet_send(session, message->buffer, message->len, 1);
368         rnet_message_del(message);
369
370         message = NULL;
371         r = rnet_recv(session, &message);
372         if (r || !message || message->len == 0) {
373                 fprintf(stderr, "error when receiving response\n");
374                 goto out;
375         }
376         switch (message->buffer[0]) {
377         case 1: /* go ahead */
378                 handle_response_text_and_file(cpf, message);
379                 break;
380         case 3: /* error */
381                 handle_response_error(message);
382                 finish = 1;
383                 break;
384         case 4:
385                 handle_response_already_found(cpf, message);
386                 finish = 1;
387                 break;
388         case 2:
389         case 5:
390                 handle_response_text_and_file(cpf, message);
391                 finish = 1;
392                 break;
393         }
394         rnet_message_del(message);
395
396         if (finish)
397                 goto out;
398
399         message = rnet_decfile_get_file(decfile);
400         rnet_send(session, message->buffer, message->len, 0);
401
402         message = NULL;
403         r = rnet_recv(session, &message);
404         if (r || !message || message->len == 0) {
405                 fprintf(stderr, "error when receiving response\n");
406                 goto out;
407         }
408         switch (message->buffer[0]) {
409         case 3: /* error */
410                 handle_response_error(message);
411                 break;
412         case 2:
413         case 4:
414         case 5:
415         case 1:
416                 handle_response_text_and_file(cpf, message);
417                 break;
418         }
419         
420 out:
421         gnutls_bye(session, GNUTLS_SHUT_RDWR);
422         close(c);
423         rnet_decfile_close(decfile);
424         gnutls_global_deinit();
425
426         return 0;
427 }