1 /** @file
2     UDP syslog output for rtl_433 events.
3 
4     Copyright (C) 2021 Christian Zuckschwerdt
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 2 of the License, or
9     (at your option) any later version.
10 */
11 
12 #include "output_udp.h"
13 
14 #include "data.h"
15 #include "abuf.h"
16 #include "r_util.h"
17 #include "fatal.h"
18 
19 #include <string.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 
23 #include <limits.h>
24 // gethostname() needs _XOPEN_SOURCE 500 on unistd.h
25 #ifndef _XOPEN_SOURCE
26 #define _XOPEN_SOURCE 500
27 #endif
28 
29 #ifndef _MSC_VER
30 #include <unistd.h>
31 #endif
32 
33 #ifdef _WIN32
34     #if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)
35     #undef _WIN32_WINNT
36     #define _WIN32_WINNT 0x0600   /* Needed to pull in 'struct sockaddr_storage' */
37     #endif
38 
39     #include <winsock2.h>
40     #include <ws2tcpip.h>
41 #else
42     #include <sys/types.h>
43     #include <sys/socket.h>
44     #include <netdb.h>
45     #include <netinet/in.h>
46 
47     #define SOCKET          int
48     #define INVALID_SOCKET  (-1)
49     #define closesocket(x)  close(x)
50 #endif
51 
52 #include <time.h>
53 
54 #ifdef _WIN32
55     #define _POSIX_HOST_NAME_MAX  128
56     #define perror(str)           ws2_perror(str)
57 
ws2_perror(const char * str)58     static void ws2_perror (const char *str)
59     {
60         if (str && *str)
61             fprintf(stderr, "%s: ", str);
62         fprintf(stderr, "Winsock error %d.\n", WSAGetLastError());
63     }
64 #endif
65 #ifdef ESP32
66     #include <tcpip_adapter.h>
67     #define _POSIX_HOST_NAME_MAX 128
68     #define gai_strerror strerror
69 #endif
70 
71 /* Datagram (UDP) client */
72 
73 typedef struct {
74     struct sockaddr_storage addr;
75     socklen_t addr_len;
76     SOCKET sock;
77 } datagram_client_t;
78 
datagram_client_open(datagram_client_t * client,const char * host,const char * port)79 static int datagram_client_open(datagram_client_t *client, const char *host, const char *port)
80 {
81     if (!host || !port)
82         return -1;
83 
84     struct addrinfo hints, *res, *res0;
85     int    error;
86     SOCKET sock;
87 
88     memset(&hints, 0, sizeof(hints));
89     hints.ai_family = PF_UNSPEC;
90     hints.ai_socktype = SOCK_DGRAM;
91     hints.ai_flags = AI_ADDRCONFIG;
92     error = getaddrinfo(host, port, &hints, &res0);
93     if (error) {
94         fprintf(stderr, "%s\n", gai_strerror(error));
95         return -1;
96     }
97     sock = INVALID_SOCKET;
98     for (res = res0; res; res = res->ai_next) {
99         sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
100         if (sock >= 0) {
101             client->sock = sock;
102             memset(&client->addr, 0, sizeof(client->addr));
103             memcpy(&client->addr, res->ai_addr, res->ai_addrlen);
104             client->addr_len = res->ai_addrlen;
105             break; // success
106         }
107     }
108     freeaddrinfo(res0);
109     if (sock == INVALID_SOCKET) {
110         perror("socket");
111         return -1;
112     }
113 
114     //int broadcast = 1;
115     //int ret = setsockopt(client->sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
116 
117     return 0;
118 }
119 
datagram_client_close(datagram_client_t * client)120 static void datagram_client_close(datagram_client_t *client)
121 {
122     if (!client)
123         return;
124 
125     if (client->sock != INVALID_SOCKET) {
126         closesocket(client->sock);
127         client->sock = INVALID_SOCKET;
128     }
129 
130 #ifdef _WIN32
131     WSACleanup();
132 #endif
133 }
134 
datagram_client_send(datagram_client_t * client,const char * message,size_t message_len)135 static void datagram_client_send(datagram_client_t *client, const char *message, size_t message_len)
136 {
137     int r =  sendto(client->sock, message, message_len, 0, (struct sockaddr *)&client->addr, client->addr_len);
138     if (r == -1) {
139         perror("sendto");
140     }
141 }
142 
143 /* Syslog UDP printer, RFC 5424 (IETF-syslog protocol) */
144 
145 typedef struct {
146     struct data_output output;
147     datagram_client_t client;
148     int pri;
149     char hostname[_POSIX_HOST_NAME_MAX + 1];
150 } data_output_syslog_t;
151 
print_syslog_data(data_output_t * output,data_t * data,char const * format)152 static void print_syslog_data(data_output_t *output, data_t *data, char const *format)
153 {
154     UNUSED(format);
155     data_output_syslog_t *syslog = (data_output_syslog_t *)output;
156 
157     // we expect a normal message around 500 bytes
158     // full stats report would be 12k and we want a max of MTU anyway
159     char message[1024];
160     abuf_t msg = {0};
161     abuf_init(&msg, message, sizeof(message));
162 
163     time_t now;
164     struct tm tm_info;
165     time(&now);
166 #ifdef _WIN32
167     gmtime_s(&tm_info, &now);
168 #else
169     gmtime_r(&now, &tm_info);
170 #endif
171     char timestamp[21];
172     strftime(timestamp, 21, "%Y-%m-%dT%H:%M:%SZ", &tm_info);
173 
174     abuf_printf(&msg, "<%d>1 %s %s rtl_433 - - - ", syslog->pri, timestamp, syslog->hostname);
175 
176     msg.tail += data_print_jsons(data, msg.tail, msg.left);
177     if (msg.tail >= msg.head + sizeof(message))
178         return; // abort on overflow, we don't actually want to send more than fits the MTU
179 
180     size_t abuf_len = msg.tail - msg.head;
181     datagram_client_send(&syslog->client, message, abuf_len);
182 }
183 
data_output_syslog_free(data_output_t * output)184 static void data_output_syslog_free(data_output_t *output)
185 {
186     data_output_syslog_t *syslog = (data_output_syslog_t *)output;
187 
188     if (!syslog)
189         return;
190 
191     datagram_client_close(&syslog->client);
192 
193     free(syslog);
194 }
195 
data_output_syslog_create(const char * host,const char * port)196 struct data_output *data_output_syslog_create(const char *host, const char *port)
197 {
198     data_output_syslog_t *syslog = calloc(1, sizeof(data_output_syslog_t));
199     if (!syslog) {
200         WARN_CALLOC("data_output_syslog_create()");
201         return NULL; // NOTE: returns NULL on alloc failure.
202     }
203 #ifdef _WIN32
204     WSADATA wsa;
205 
206     if (WSAStartup(MAKEWORD(2,2),&wsa) != 0) {
207         perror("WSAStartup()");
208         free(syslog);
209         return NULL;
210     }
211 #endif
212 
213     syslog->output.print_data   = print_syslog_data;
214     syslog->output.output_free  = data_output_syslog_free;
215     // Severity 5 "Notice", Facility 20 "local use 4"
216     syslog->pri = 20 * 8 + 5;
217     #ifdef ESP32
218     const char* adapter_hostname = NULL;
219     tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &adapter_hostname);
220     if (adapter_hostname) {
221         memcpy(syslog->hostname, adapter_hostname, _POSIX_HOST_NAME_MAX);
222     }
223     else {
224         syslog->hostname[0] = '\0';
225     }
226     #else
227     gethostname(syslog->hostname, _POSIX_HOST_NAME_MAX + 1);
228     #endif
229     syslog->hostname[_POSIX_HOST_NAME_MAX] = '\0';
230     datagram_client_open(&syslog->client, host, port);
231 
232     return &syslog->output;
233 }
234