daemon designed to read short data bursts from weather stations transmitting over the Iridium satellite network
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

563 lines
13 KiB

  1. #define _POSIX_C_SOURCE 1
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. #include <time.h>
  5. #include <errno.h>
  6. #include <string.h>
  7. #include <stdlib.h>
  8. #include <limits.h>
  9. #include <fcntl.h>
  10. #include <sys/stat.h>
  11. #include <sys/types.h>
  12. #include <sys/socket.h>
  13. #include <sys/time.h>
  14. #include <sys/param.h>
  15. #include <netinet/in.h>
  16. #include <arpa/inet.h>
  17. #include <libgen.h>
  18. #include <getopt.h>
  19. #include <poll.h>
  20. #include <syslog.h>
  21. #include <signal.h>
  22. #include <inttypes.h>
  23. #include "wstationd.h"
  24. #include "imei.h"
  25. // As of the writing of this daemon, SBD maximum message length
  26. // is hardware limited to 1960 bytes, however, the SBD format
  27. // has a maximum size of 64k per burst. We add three bytes
  28. // for the SBD header itself. This value largely
  29. // dictates the memory cost per connection.
  30. #define BUFFERSIZE 65539
  31. // Timeout for each connection, in seconds
  32. #define TIMEOUT 30
  33. static char *exec_name;
  34. // Should this application daemonize?
  35. static int daemonize = 1;
  36. // Hard limit on number of concurrent connections
  37. static int slotlimit = 128;
  38. // Used to handle signaled shutdowns
  39. static int keep_polling = 1;
  40. struct connection
  41. {
  42. // Buffer to store the data stream before the dump to file
  43. unsigned char buff[BUFFERSIZE];
  44. ssize_t len; // How many bytes have been written to the buffer
  45. struct timeval tv; // Time connection was made
  46. struct in_addr addr; // Address connection was made from
  47. };
  48. static int write_connection(struct connection *, int strip);
  49. void shutdown_handler(int sig)
  50. {
  51. keep_polling = 0;
  52. }
  53. static void print_help()
  54. {
  55. fprintf(stdout, "usage: %s [-d] [-l limit] [-p port] [-b addr] directory\n", exec_name);
  56. fprintf(stdout, " -d, --nodaemon Do not detach and daemonize\n");
  57. fprintf(stdout, " -l, --limit <limit> Set concurrent connection limit (default: 128)\n");
  58. fprintf(stdout, " -p, --port <port> Port to listen on (default: 10800)\n");
  59. fprintf(stdout, " -b, --bind <address> Address to bind to (default: 0.0.0.0)\n");
  60. fprintf(stdout, " -i, --imei <imei> IMEI to allow - specify multiple times for\n");
  61. fprintf(stdout, " additional IMEIs (default: allow all)\n");
  62. fprintf(stdout, " -s, --strip Strip Iridium SBD header when writing files\n");
  63. fprintf(stdout, " -h, --help Print this message and exit\n");
  64. fprintf(stdout, " -v, --version Print version and exit\n");
  65. }
  66. int main(int argc, char* argv[])
  67. {
  68. const struct option longopts[] = {
  69. { "imei", required_argument, NULL, 'i' },
  70. { "limit", required_argument, NULL, 'l' },
  71. { "nodaemon", required_argument, NULL, 'd' },
  72. { "port", required_argument, NULL, 'p' },
  73. { "bind", required_argument, NULL, 'b' },
  74. { "strip", no_argument, NULL, 's' },
  75. { "version", no_argument, NULL, 'v' },
  76. { "help", no_argument, NULL, 'h' },
  77. { NULL, 0, NULL, 0 }
  78. };
  79. int flags = 0;
  80. int sockfd = 0;
  81. int port = 10800; // Default port
  82. uint32_t address = INADDR_ANY; // Default listen address
  83. struct connection** conns = NULL;
  84. struct pollfd* fds = NULL;
  85. imei_set* imeis = NULL;
  86. uint64_t imei = 0;
  87. int strip = 0;
  88. if((exec_name = basename(argv[0])) == NULL){
  89. fprintf(stderr, "%s: cannot get basename - %s\n",
  90. exec_name, strerror(errno)
  91. );
  92. goto shutdown_error;
  93. }
  94. for(int ch; (ch = getopt_long(argc, argv, "i:l:b:p:dsvh0", longopts, NULL)) != -1;){
  95. switch(ch){
  96. case 'd':
  97. daemonize = 0;
  98. break;
  99. case 'l':
  100. slotlimit = (int)strtol(optarg, NULL, 10);
  101. if(slotlimit < 1 || slotlimit > 1024){
  102. fprintf(stderr, "%s: invalid limit (must be between 1 and 1024)\n", exec_name);
  103. print_help();
  104. goto shutdown_error;
  105. }
  106. break;
  107. case 'i':
  108. imei = imei_uint64(
  109. (unsigned char *)argv[optind-1],
  110. strlen(argv[optind-1])
  111. );
  112. if(imei == UINT64_MAX){
  113. fprintf(stderr, "%s: invalid IMEI \"%s\"\n", exec_name, argv[optind-1]);
  114. goto shutdown_error;
  115. }
  116. if(imeis == NULL){
  117. imeis = imei_set_new(255);
  118. if(imeis == NULL){
  119. fprintf(stderr, "%s: cannot create IMEI set (out of memory?)\n", exec_name);
  120. goto shutdown_error;
  121. }
  122. }
  123. // TODO: Probably should be some error handling here
  124. imei_set_add(&imeis, imei);
  125. break;
  126. case 's':
  127. strip = 1;
  128. break;
  129. case 'p':
  130. port = (int)strtol(optarg, NULL, 10);
  131. if(port < 1 || port > 65535){
  132. fprintf(stderr, "%s: invalid port number\n", exec_name);
  133. print_help();
  134. goto shutdown_error;
  135. }
  136. break;
  137. case 'b':
  138. if(inet_pton(AF_INET, optarg, &address) == -1){
  139. fprintf(stderr, "%s: invalid bind address\n", exec_name);
  140. print_help();
  141. goto shutdown_error;
  142. }
  143. break;
  144. case 'v':
  145. fprintf(stdout, "wstationd v%s (%s %s)\n", VERSION,__DATE__, __TIME__);
  146. goto shutdown_clean;
  147. case '?':
  148. case 'h':
  149. print_help();
  150. goto shutdown_clean;
  151. }
  152. }
  153. // Accept final argument, the directory
  154. if(argc == (optind+1)){
  155. // Fail if we can't chdir to that directory
  156. if(chdir(argv[argc-1]) == -1){
  157. fprintf(stderr, "%s: cannot change directory - %s\n",
  158. exec_name, strerror(errno)
  159. );
  160. goto shutdown_error;
  161. }
  162. } else {
  163. print_help();
  164. goto shutdown_error;
  165. }
  166. // Handle signals
  167. signal(SIGINT, shutdown_handler);
  168. // Daemonize if requested
  169. if(daemonize){
  170. pid_t pid = fork();
  171. // Fork failure
  172. if(pid == -1){
  173. fprintf(stderr, "%s: cannot fork - %s\n",
  174. exec_name, strerror(errno)
  175. );
  176. goto shutdown_error;
  177. }
  178. // Fork success, exit parent process
  179. if(pid > 0){ exit(EXIT_SUCCESS); }
  180. // create new session for daemon
  181. if(setsid() == -1){
  182. fprintf(stderr, "%s: cannot setsid - %s\n",
  183. exec_name, strerror(errno)
  184. );
  185. goto shutdown_error;
  186. }
  187. // Ensure that standard descriptors are
  188. // unavailable after fork() and setsid()
  189. int fd = open("/dev/null", O_RDWR, 0);
  190. if(fd != -1){
  191. dup2(fd, fileno(stdin));
  192. dup2(fd, fileno(stdout));
  193. dup2(fd, fileno(stderr));
  194. }
  195. // Open syslog
  196. openlog(exec_name, LOG_PID, LOG_DAEMON);
  197. }
  198. // Change default mask
  199. umask(0);
  200. // Establish the socket
  201. sockfd = socket(AF_INET, SOCK_STREAM, 0);
  202. if(sockfd == -1){
  203. if(daemonize){
  204. syslog(LOG_ERR, "cannot open socket - %s",
  205. strerror(errno)
  206. );
  207. } else {
  208. fprintf(stderr, "%s: cannot open socket - %s\n",
  209. exec_name, strerror(errno)
  210. );
  211. }
  212. goto shutdown_error;
  213. }
  214. // This sets REUSE on the socket so it's easily reallocated if
  215. // this program dies
  216. flags = 1;
  217. setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags));
  218. struct sockaddr_in sock;
  219. memset(&sock, 0, sizeof(sock));
  220. sock.sin_family = AF_INET;
  221. sock.sin_addr.s_addr = address;
  222. sock.sin_port = htons(port);
  223. // Bind our socket to the specified address and port
  224. if(bind(sockfd, (struct sockaddr*)&sock, sizeof(sock)) < 0){
  225. if(daemonize){
  226. syslog(LOG_ERR, "cannot bind - %s",
  227. strerror(errno)
  228. );
  229. } else {
  230. fprintf(stderr, "%s: cannot bind - %s\n",
  231. exec_name, strerror(errno)
  232. );
  233. }
  234. goto shutdown_error;
  235. }
  236. // Listen on port
  237. if(listen(sockfd, 10) != 0){
  238. if(daemonize){
  239. syslog(LOG_ERR, "cannot listen - %s",
  240. strerror(errno)
  241. );
  242. } else {
  243. fprintf(stderr, "%s: cannot listen - %s\n",
  244. exec_name, strerror(errno)
  245. );
  246. }
  247. goto shutdown_error;
  248. }
  249. fds = malloc(sizeof(struct pollfd) * (slotlimit + 1));
  250. conns = malloc(sizeof(struct connection*) * slotlimit);
  251. if(conns == NULL || fds == NULL){
  252. if(daemonize){ syslog(LOG_ERR, "out of memory"); }
  253. else { fprintf(stderr, "%s: out of memory\n", exec_name); }
  254. }
  255. memset(conns, 0, sizeof(struct connection*) * slotlimit);
  256. memset(fds, 0, sizeof(struct pollfd) * (slotlimit + 1));
  257. // Add sockfd
  258. fds[0].fd = sockfd;
  259. fds[0].events = POLLIN;
  260. fds[0].revents = 0;
  261. while(keep_polling){
  262. if(poll(fds, (slotlimit + 1), 1000) == -1){
  263. // Ignore interrupt generated errors
  264. if(errno != EINTR){
  265. if(daemonize){
  266. syslog(LOG_ERR, "socket poll error - %s",
  267. strerror(errno)
  268. );
  269. } else {
  270. fprintf(stderr, "%s: socket poll error - %s\n",
  271. exec_name, strerror(errno)
  272. );
  273. }
  274. goto shutdown_error;
  275. }
  276. }
  277. // Handle existing connections
  278. int slotfirst = 0, slotfree = 0;
  279. for(int i = 1; i <= slotlimit; i++){
  280. // Handle event
  281. if(fds[i].revents & POLLIN){
  282. ssize_t sz = recv(
  283. fds[i].fd, conns[i-1]->buff,
  284. (BUFFERSIZE - conns[i-1]->len), 0
  285. );
  286. if(sz > 0) conns[i-1]->len += sz;
  287. else {
  288. if(conns[i-1]->len > 25){
  289. uint16_t sbd_sz = *(uint16_t*)(&conns[i-1]->buff[1]);
  290. // Convert and add three bytes for the SBD header
  291. ssize_t expected = (ssize_t)htons(sbd_sz) + 3;
  292. int ok = 1;
  293. // Is the reported size the same as the actual size
  294. // written in bytes?
  295. if(expected != conns[i-1]->len) ok = 0;
  296. // Is the protocol byte in place?
  297. if(ok && conns[i-1]->buff[0] != 1) ok = 0;
  298. // Parse connection's IMEI
  299. uint64_t imei = imei_uint64((conns[i-1]->buff + 10), 15);
  300. // Check for invalid IMEIs
  301. if(ok && imei == UINT64_MAX) ok = 0;
  302. // If everything is ok so far, and
  303. // we have a list of approved IMEIs,
  304. // check it for this IMEI
  305. if(ok && imeis != NULL && !imei_set_search(imeis, imei)){
  306. ok = 0;
  307. if(daemonize){
  308. syslog(LOG_NOTICE, "IMEI rejected: %.*s",
  309. 15, (conns[i-1]->buff + 10)
  310. );
  311. } else {
  312. fprintf(stderr, "%s: IMEI rejected: %.*s\n",
  313. exec_name, 15, (conns[i-1]->buff + 10)
  314. );
  315. }
  316. }
  317. if(ok) write_connection(conns[i - 1], strip);
  318. }
  319. free(conns[i - 1]);
  320. conns[i - 1] = NULL;
  321. close(fds[i].fd);
  322. fds[i].fd = 0;
  323. fds[i].events = 0;
  324. }
  325. // Sweep up dead connections
  326. } else if(fds[i].fd){
  327. struct timeval tv;
  328. gettimeofday(&tv, NULL);
  329. if((tv.tv_sec - conns[i - 1]->tv.tv_sec) >= TIMEOUT){
  330. free(conns[i - 1]);
  331. conns[i - 1] = NULL;
  332. close(fds[i].fd);
  333. fds[i].fd = fds[i].events = 0;
  334. }
  335. }
  336. // Count total free slots
  337. if(!fds[i].fd){
  338. ++slotfree;
  339. // Note the first available slot
  340. if(!slotfirst) slotfirst = i;
  341. }
  342. // Clear events
  343. fds[i].revents = 0;
  344. }
  345. // Handle new connections
  346. if((fds[0].revents & POLLIN) && slotfree){
  347. struct sockaddr_in client;
  348. socklen_t client_sz = sizeof(client);
  349. int fd = accept(sockfd, (struct sockaddr*)&client, &client_sz);
  350. if(fd == -1){
  351. if(daemonize){
  352. syslog(LOG_ERR, "accept failed - %s",
  353. strerror(errno)
  354. );
  355. } else {
  356. fprintf(stderr, "%s: accept failed - %s\n",
  357. exec_name, strerror(errno)
  358. );
  359. }
  360. continue;
  361. }
  362. // Get current flags before setting nonblock
  363. if((flags = fcntl(fd, F_GETFL)) == -1){
  364. if(daemonize){
  365. syslog(LOG_ERR, "cannot get connection flags - %s",
  366. strerror(errno)
  367. );
  368. } else {
  369. fprintf(stderr, "%s: cannot get connection flags - %s\n",
  370. exec_name, strerror(errno)
  371. );
  372. }
  373. close(fd);
  374. continue;
  375. }
  376. // Set nonblocking on socket
  377. if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1){
  378. if(daemonize){
  379. syslog(LOG_ERR, "cannot set connection flags - %s",
  380. strerror(errno)
  381. );
  382. } else {
  383. fprintf(stderr, "%s: cannot set connection flags - %s\n",
  384. exec_name, strerror(errno)
  385. );
  386. }
  387. close(fd);
  388. continue;
  389. }
  390. // Allocate memory for the connection's state.
  391. conns[slotfirst - 1] = malloc(sizeof(struct connection));
  392. if(conns[slotfirst - 1] == NULL){
  393. if(daemonize){
  394. syslog(LOG_ERR, "out of memory");
  395. } else {
  396. fprintf(stderr, "%s: out of memory\n", exec_name);
  397. }
  398. close(fd);
  399. continue;
  400. }
  401. conns[slotfirst - 1]->len = 0; // reset buffer
  402. conns[slotfirst - 1]->addr = client.sin_addr; // save source ip
  403. gettimeofday(&conns[slotfirst - 1]->tv, NULL); // mark connection time
  404. // Add to poll list
  405. fds[slotfirst].fd = fd;
  406. fds[slotfirst].events = POLLIN;
  407. fds[slotfirst].revents = 0;
  408. slotfree--;
  409. fds[0].revents = 0;
  410. }
  411. // Reset polling event on listener socket -
  412. // if there's no free slots, stop polling the listener
  413. if(slotfree) fds[0].events = POLLIN;
  414. else fds[0].events = 0;
  415. }
  416. shutdown_clean:
  417. if(sockfd > 0) close(sockfd);
  418. if(fds != NULL){ free(fds); fds = NULL; }
  419. if(conns != NULL){
  420. for(int i = 0; i < slotlimit; i++){
  421. if(conns[i] != NULL) free(conns[i]);
  422. }
  423. free(conns); conns = NULL;
  424. }
  425. return 0;
  426. shutdown_error:
  427. if(sockfd > 0) close(sockfd);
  428. if(fds != NULL){ free(fds); fds = NULL; }
  429. if(conns != NULL){
  430. for(int i = 0; i < slotlimit; i++){
  431. if(conns[i] != NULL) free(conns[i]);
  432. }
  433. free(conns); conns = NULL;
  434. }
  435. return 1;
  436. }
  437. static int write_connection(struct connection *conn, int strip)
  438. {
  439. // Build a filename for the file we're writing based
  440. // on the time and the source IP address
  441. char timebuff[20];
  442. struct tm timeptr;
  443. localtime_r(&conn->tv.tv_sec, &timeptr);
  444. strftime(&timebuff[0], 20, "%Y%m%d%H%M%S", &timeptr);
  445. char fnbuff[50];
  446. snprintf(&fnbuff[0], 50, "%s.%ld-%s.dat",
  447. timebuff, (long)conn->tv.tv_usec,
  448. inet_ntoa(conn->addr)
  449. );
  450. int ofd;
  451. if((ofd = open(fnbuff, O_WRONLY | O_CREAT, 0644)) == -1){
  452. if(daemonize){
  453. syslog(LOG_ERR, "cannot open %s - %s",
  454. fnbuff, strerror(errno)
  455. );
  456. } else {
  457. fprintf(stderr, "%s: cannot open %s - %s\n",
  458. exec_name, fnbuff, strerror(errno)
  459. );
  460. }
  461. return 1;
  462. }
  463. unsigned char *buff = (strip ? (conn->buff + 3) : conn->buff);
  464. ssize_t len = (strip ? (conn->len - 3) : conn->len);
  465. // Write out the buffer
  466. if(write(ofd, buff, len) == -1){
  467. if(daemonize){
  468. syslog(LOG_ERR, "write error on %s - %s",
  469. fnbuff, strerror(errno)
  470. );
  471. } else {
  472. fprintf(stderr, "%s: write error on %s - %s\n",
  473. exec_name, fnbuff, strerror(errno)
  474. );
  475. }
  476. close(ofd);
  477. return 1;
  478. }
  479. close(ofd);
  480. return 0;
  481. }