ootp/otp-tokend/otp-tokend.c

398 lines
9.3 KiB
C

#include <errno.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <curl/curl.h>
#include <ctype.h>
#include "xerr.h"
#include "otplib.h"
/*
* XXX man page
*/
static void usage(void);
static int write_pidfile(char *fname);
#define REQ_MODE_HTTP 0x1
#define REQ_MODE_SMTP 0x2
#define NXT_FIELD(V1,V2)\
f = strsep(&c, "\n");\
if (!f) {\
xerr_warnx("parse rx_buf fail at %s", V1);\
continue;\
}\
V2 = c;\
size_t curl_write_cb(void *ptr, size_t size, size_t nmemb, void *userdata);
size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *userdata);
char *global_token;
char *global_svc;
char *global_hdr_subject;
char *global_hdr_from;
int main(int argc, char **argv)
{
extern char *ootp_version;
struct sockaddr_un rx_path;
pid_t pid_child;
CURL *curl;
struct curl_slist *smtp_rcpt = NULL;
char rx_buf[1024], *c, *f, *msg_svc, *msg_user, *msg_loc, *msg_token;
char msg_buf[1024], post_buf[1024], *msg_ue, *loc_ue, *rx_pathname;
char buf[1024], *pid_fname, *url_http, *url_smtp, *url, *hdr_from;
char *hdr_subject;
int rx_sock, len, verbose, opt_version, daemon_mode, buf_l, i;
int req_mode, isdigits, isemail;
struct option longopts[] = {
{ "bind-path", 1, (void*)0L, 'b'},
{ "disable-daemon-mode", 0, (void*)0L, 'D'},
{ "from-address", 1, (void*)0L, 'f'},
{ "help", 0, (void*)0L, 'h'},
{ "help", 0, (void*)0L, '?'},
{ "subject", 1, (void*)0L, 's'},
{ "smtp-url", 1, (void*)0L, 'S'},
{ "pidfile", 1, (void*)0L, 'P'},
{ "http-url", 1, (void*)0L, 'H'},
{ "verbose", 0, (void*)0L, 'v'},
{ "version", 1, &opt_version, 1},
{ 0, 0, 0, 0},
};
req_mode = 0;
daemon_mode = 1;
opt_version = 0;
smtp_rcpt = (struct curl_slist*)0L;
pid_fname = "/var/run/otp-tokend.pid";
url_http = (char*)0L;
url_smtp = (char*)0L;
url = (char*)0L;
hdr_from = "hotp@eng.oar.net";
hdr_subject = "HOTP Token";
verbose = 0;
xerr_setid(argv[0]);
rx_pathname = OTP_SEND_TOKEN_PATHNAME;
while ((i = getopt_long(argc, argv, "b:Df:h?H:P:s:S:v", longopts,
(int*)0L)) != -1) {
switch (i) {
case 'b':
rx_pathname = optarg;
break;
case 'D':
daemon_mode = 0;
break;
case 'f':
hdr_from = optarg;
break;
case 'h':
case '?':
usage();
exit(0);
break;
case 'P':
pid_fname = optarg;
break;
case 's':
hdr_subject = optarg;
break;
case 'S':
url_smtp = optarg;
break;
case 'H':
url_http = optarg;
break;
case 'v':
++verbose;
break;
case 0:
if (opt_version) {
printf("%s\n", ootp_version);
exit(0);
}
default:
xerr_errx(1, "getopt_long(): fatal.");
} /* switch */
} /* while getopt_long() */
global_hdr_subject = hdr_subject;
global_hdr_from = hdr_from;
if (!url_http || !url_smtp)
xerr_errx(1, "HTTP and SMTP url required.");
if (daemon_mode) {
xerr_setsyslog2(1); /* use syslog for output */
/* run in the background */
if (daemon(0, 0) < 0)
xerr_err(1, "dameon()");
} /* daemon_mode */
buf_l = snprintf(buf, sizeof(buf), "tokend start:");
if (verbose > 1) {
for (i = 0; i < argc; ++i)
buf_l += snprintf(buf+buf_l, sizeof(buf)-buf_l, " %s", argv[i]);
}
xerr_info(buf);
bzero(&rx_path, sizeof (rx_path));
rx_path.sun_family = AF_UNIX;
if (strlen(rx_pathname) >= sizeof(rx_path.sun_path))
xerr_errx(1, "rx_pathname too long.");
strncpy(rx_path.sun_path, rx_pathname, sizeof(rx_path.sun_path));
/* write out pidfile */
if (write_pidfile(pid_fname) < 0)
xerr_errx(1, "write_pidfile(%s): fatal", buf);
if ((rx_sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
xerr_err(1, "socket()");
/* fail silently */
umask(077);
unlink(rx_pathname);
if (bind(rx_sock, (struct sockaddr*)&rx_path, sizeof(rx_path)) < 0)
xerr_err(1, "bind(%s)", rx_pathname);
/* reap children */
if (signal(SIGCHLD, SIG_IGN) == SIG_ERR)
xerr_errx(1, "signal(SIGCHLD)");
while (1) {
if ((len = recv(rx_sock, &rx_buf, sizeof(rx_buf), 0)) < 0)
xerr_err(1, "recv()");
if ((pid_child = fork()) == -1)
xerr_err(1, "fork()");
/* parent? */
if (pid_child)
continue;
/* child */
if (verbose > 2)
xerr_info("Child pid=%lu.", (unsigned long)getpid());
if (len == 0)
xerr_errx(1, "rx_buf empty.");
if (rx_buf[len - 1] != 0)
xerr_errx(1, "recv(): rx_buf not null terminated, skipping.");
c = rx_buf;
msg_svc = rx_buf;
NXT_FIELD("msg_user", msg_user);
NXT_FIELD("msg_loc", msg_loc);
NXT_FIELD("msg_token", msg_token);
for (c = msg_token; *c; ++c)
if (*c == '\n')
*c = 0;
/* guess destination. All digits == http, @ == smtp */
isdigits = 1;
isemail = 0;
for (c = msg_loc; *c; ++c) {
if (!isdigit(*c))
isdigits = 0;
if (*c == '@')
isemail = 1;
}
if (isdigits) {
req_mode = REQ_MODE_HTTP;
url = url_http;
} else if (isemail) {
req_mode = REQ_MODE_SMTP;
url = url_smtp;
} else {
xerr_errx(1, "Req mode not set for %s.", msg_loc);
}
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
xerr_errx(1, "curl_global_init(): failed.");
if (!(curl = curl_easy_init()))
xerr_errx(1, "curl_easy_init()");
if (curl_easy_setopt(curl, CURLOPT_URL, url) != CURLE_OK)
xerr_errx(1, "curl_easy_setopt(url): failed.");
if (verbose > 1)
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
if (req_mode == REQ_MODE_HTTP) {
if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
&curl_write_cb) != CURLE_OK)
xerr_errx(1, "curl_easy_setopt(CURLOPT_WRITEFUNCTION): failed.");
snprintf(msg_buf, sizeof(msg_buf), "%s: %s", msg_svc, msg_token);
if (!(msg_ue = curl_escape(msg_buf, 0)))
xerr_errx(1, "curl_escape(%s): failed.", msg_buf);
if (!(loc_ue = curl_escape(msg_loc, 0))) {
free(msg_ue);
xerr_errx(1, "curl_escape(%s): failed.", msg_loc);
}
snprintf(post_buf, sizeof(post_buf), "to=%s&msg=%s", loc_ue, msg_ue);
if (curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_buf) != CURLE_OK)
xerr_errx(1, "curl_easy_setopt(CURLOPT_POSTFIELDS, %s): failed.",
post_buf);
if (curl_easy_perform(curl) != CURLE_OK)
xerr_errx(1, "curl_easy_perform(): failed.");
if (verbose > 1)
xerr_info("msg_buf=%s", msg_buf);
curl_easy_cleanup(curl);
curl_global_cleanup();
} else if (req_mode == REQ_MODE_SMTP) {
if (curl_easy_setopt(curl, CURLOPT_MAIL_FROM, hdr_from) != CURLE_OK)
xerr_errx(1, "curl_easy_setopt(CURLOPT_MAIL_FROM): failed.");
if (!(smtp_rcpt = curl_slist_append(smtp_rcpt, msg_loc)))
xerr_errx(1, "curl_slist_append(smtp_rcpt, msg_loc): failed.");
if (curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, smtp_rcpt) != CURLE_OK)
xerr_errx(1, "curl_easy_setopt(CURLOPT_MAIL_RCPT): failed.");
/* needed by read_cb */
global_token = msg_token;
global_svc = msg_svc;
if (curl_easy_setopt(curl, CURLOPT_READFUNCTION,
&curl_read_cb) != CURLE_OK)
xerr_errx(1, "curl_easy_setopt(CURLOPT_READFUNCTION): failed.");
if (curl_easy_perform(curl) != CURLE_OK)
xerr_errx(1, "curl_easy_perform(): failed.");
curl_slist_free_all(smtp_rcpt);
} else {
xerr_errx(1, "req_mode");
}
/* exit child */
if (verbose > 2)
xerr_info("child exit");
exit(0);
} /* forever waiting messages */
} /* main */
size_t curl_write_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
{
char *c;
c = malloc((size*nmemb)+1);
bcopy(ptr, c, size*nmemb);
c[size*nmemb] = 0;
xerr_info(c);
free(c);
return size*nmemb;
}
size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
{
size_t t, r;
static int cd;
if (cd == 0) {
t = size*nmemb;
r = snprintf(ptr, t,
"From: %s <HOTP>\r\nSubject: %s\r\n\r\nToken for %s: %s\r\n",
global_hdr_from, global_hdr_subject, global_svc, global_token);
++cd;
return r;
} else {
return 0;
}
}
void usage(void)
{
extern char *ootp_version;
fprintf(stderr, "otp-tokend [-?Dhv] [-b bind-path] [-f from-address] [-s subject]\n");
fprintf(stderr, " -S smtp-url [-P pidfile] -H http-url\n");
printf("%s\n", ootp_version);
}
/*
* function: write_pidfile()
*
* Store proces ID in ASII to fname.
*
* returns: < 0 : fail
* 0 : success
*/
int write_pidfile(char *fname)
{
int fd, buf_l;
char buf[512];
buf_l = snprintf(buf, sizeof(buf), "%lu\n", (unsigned long)getpid());
if ((fd = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0 ) {
xerr_warn("open(%s)", fname);
return -1;
}
if (write(fd, buf, buf_l) < 0) {
xerr_warn("write(%s)", fname);
close (fd);
return -1;
}
return (close(fd));
} /* write_pidfile */