work yet. The mail comes from mail_sender which MUST be configured.
sendmail() sends mail via the SMTP server specified in the config
file. The default is localhost. inputline() is extended to cope with
CRLF-flavored protocols.
local_hostname() returns the FQDN of the local host (assuming you
didn't screw up your /etc/hosts).
mime_encode_text() and mime_to_qp() prepare text/* messages for
sending in mail.
hash.c hash.h \
heap.h \
hex.c hex.h \
+ hostname.c hostname.h \
ifreq.c ifreq.h \
inputline.c inputline.h \
kvp.c kvp.h \
rights.c queue-rights.c rights.h \
rtp.h \
selection.c selection.h \
+ sendmail.c sendmail.h \
signame.c signame.h \
sink.c sink.h \
speaker-protocol.c speaker-protocol.h \
{ C(home), &type_string, validate_isabspath },
{ C(listen), &type_stringlist, validate_port },
{ C(lock), &type_boolean, validate_any },
+ { C(mail_sender), &type_string, validate_any },
{ C(mixer), &type_string, validate_ischr },
{ C(multicast_loop), &type_boolean, validate_any },
{ C(multicast_ttl), &type_integer, validate_non_negative },
{ C(scratch), &type_string_accum, validate_isreg },
{ C(short_display), &type_integer, validate_positive },
{ C(signal), &type_signal, validate_any },
+ { C(smtp_server), &type_string, validate_any },
{ C(sox_generation), &type_integer, validate_non_negative },
{ C(speaker_backend), &type_backend, validate_any },
{ C(speaker_command), &type_string, validate_any },
c->dbversion = 2;
c->cookie_login_lifetime = 86400;
c->cookie_key_lifetime = 86400 * 7;
+ c->smtp_server = xstrdup("127.0.0.1");
if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
exit(1);
return c;
/** @brief Default rights for a new user */
char *default_rights;
+
+ /** @brief SMTP server for sending mail */
+ char *smtp_server;
+
+ /** @brief Origin address for outbound mail */
+ char *mail_sender;
/* derived values: */
int nparts; /* number of distinct name parts */
--- /dev/null
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2007 Richard Kettlewell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <config.h>
+#include "types.h"
+
+#include <sys/utsname.h>
+#include <errno.h>
+#include <netdb.h>
+
+#include "mem.h"
+#include "hostname.h"
+#include "log.h"
+
+static const char *hostname;
+
+/** @brief Return the local hostname
+ * @return Hostname
+ */
+const char *local_hostname(void) {
+ if(!hostname) {
+ struct utsname u;
+ struct hostent *he;
+
+ if(uname(&u) < 0)
+ fatal(errno, "error calling uname");
+ if(!(he = gethostbyname(u.nodename)))
+ fatal(0, "cannot resolve '%s'", u.nodename);
+ hostname = xstrdup(he->h_name);
+ }
+ return hostname;
+}
+
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
--- /dev/null
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2007 Richard Kettlewell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef HOSTNAME_H
+#define HOSTNAME_H
+
+const char *local_hostname(void);
+
+#endif /* HOSTNAME_H */
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
* @param tag Used in error messages
* @param fp Stream to read from
* @param lp Where to store newly allocated string
- * @param newline Newline character
+ * @param newline Newline character or @ref CRLF
* @return 0 on success, -1 on error or eof.
*
* The newline is not included in the string. If the last line of a
* stream does not have a newline then that line is still returned.
*
+ * If @p newline is @ref CRLF then the line is terminated by CR LF,
+ * not by a single newline character. The CRLF is still not included
+ * in the string in this case.
+ *
* @p *lp is only set if the return value was 0.
*/
int inputline(const char *tag, FILE *fp, char **lp, int newline) {
dynstr_init(&d);
while((ch = getc(fp)),
- (!ferror(fp) && !feof(fp) && ch != newline))
+ (!ferror(fp) && !feof(fp) && ch != newline)) {
dynstr_append(&d, ch);
+ if(newline == CRLF && d.nvec >= 2
+ && d.vec[d.nvec - 2] == 0x0D && d.vec[d.nvec - 1] == 0x0A) {
+ d.nvec -= 2;
+ break;
+ }
+ }
if(ferror(fp)) {
error(errno, "error reading %s", tag);
return -1;
* them (excluding @newline@) via @lp@. Return 0 on success, -1 on
* error/eof. */
+/** @brief Magic @p newline value to make inputline() insist on CRLF */
+#define CRLF 0x100
+
#endif /* INPUTLINE_H */
/*
return d->vec;
}
+/** @brief Return true if @p ptr points at trailing space */
+static int is_trailing_space(const char *ptr) {
+ if(*ptr == ' ' || *ptr == '\t') {
+ while(*ptr == ' ' || *ptr == '\t')
+ ++ptr;
+ return *ptr == '\n' || *ptr == 0;
+ } else
+ return 0;
+}
+
+/** @brief Encoding text as quoted-printable
+ * @param text String to encode
+ * @return Encoded string
+ *
+ * See <a href="http://tools.ietf.org/html/rfc2045#section-6.7">RFC2045
+ * s6.7</a>.
+ */
+char *mime_to_qp(const char *text) {
+ struct dynstr d[1];
+ int linelength = 0; /* length of current line */
+ char buffer[10];
+
+ dynstr_init(d);
+ /* The rules are:
+ * 1. Anything except newline can be replaced with =%02X
+ * 2. Newline, 33-60 and 62-126 stand for themselves (i.e. not '=')
+ * 3. Non-trailing space/tab stand for themselves.
+ * 4. Output lines are limited to 76 chars, with =<newline> being used
+ * as a soft line break
+ * 5. Newlines aren't counted towards the 76 char limit.
+ */
+ while(*text) {
+ const int c = (unsigned char)*text;
+ if(c == '\n') {
+ /* Newline stands as itself */
+ dynstr_append(d, '\n');
+ linelength = 0;
+ } else if((c >= 33 && c <= 126 && c != '=')
+ || ((c == ' ' || c == '\t')
+ && !is_trailing_space(text))) {
+ /* Things that can stand for themselves */
+ dynstr_append(d, c);
+ ++linelength;
+ } else {
+ /* Anything else that needs encoding */
+ snprintf(buffer, sizeof buffer, "=%02X", c);
+ dynstr_append_string(d, buffer);
+ linelength += 3;
+ }
+ ++text;
+ if(linelength > 73 && *text && *text != '\n') {
+ /* Next character might overflow 76 character limit if encoded, so we
+ * insert a soft break */
+ dynstr_append_string(d, "=\n");
+ linelength = 0;
+ }
+ }
+ /* Ensure there is a final newline */
+ if(linelength)
+ dynstr_append(d, '\n');
+ /* That's all */
+ dynstr_terminate(d);
+ return d->vec;
+}
+
+/** @brief Encode text
+ * @param text Underlying UTF-8 text
+ * @param charsetp Where to store charset string
+ * @param encodingp Where to store encoding string
+ * @return Encoded text (might be @ref text)
+ */
+const char *mime_encode_text(const char *text,
+ const char **charsetp,
+ const char **encodingp) {
+ const char *ptr;
+
+ /* See if there are in fact any non-ASCII characters */
+ for(ptr = text; *ptr; ++ptr)
+ if((unsigned char)*ptr >= 128)
+ break;
+ if(!*ptr) {
+ /* Plain old ASCII, no encoding required */
+ *charsetp = "us-ascii";
+ *encodingp = "7bit";
+ return text;
+ }
+ *charsetp = "utf-8";
+ *encodingp = "quoted-printable";
+ return mime_to_qp(text);
+}
+
/*
Local Variables:
c-basic-offset:2
const struct cookie *find_cookie(const struct cookiedata *cd,
const char *name);
char *quote822(const char *s, int force);
+char *mime_to_qp(const char *text);
+const char *mime_encode_text(const char *text,
+ const char **charsetp,
+ const char **encodingp);
#endif /* MIME_H */
--- /dev/null
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2007 Richard Kettlewell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <config.h>
+#include "types.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <gcrypt.h>
+
+#include "syscalls.h"
+#include "log.h"
+#include "configuration.h"
+#include "inputline.h"
+#include "addr.h"
+#include "hostname.h"
+#include "sendmail.h"
+#include "base64.h"
+
+/** @brief Get a server response
+ * @param tag Server name
+ * @param in Input stream
+ * @return Response code 0-999 or -1 on error
+ */
+static int getresponse(const char *tag, FILE *in) {
+ char *line;
+
+ while(!inputline(tag, in, &line, CRLF)) {
+ if(line[0] >= '0' && line[0] <= '9'
+ && line[1] >= '0' && line[1] <= '9'
+ && line[2] >= '0' && line[2] <= '9') {
+ const int rc = 10 * (10 * line[0] + line[1]) + line[2] - 111 * '0';
+ if(rc >= 400 && rc <= 599)
+ error(0, "%s: %s", tag, line);
+ if(line[3] != '-') {
+ return rc;
+ }
+ /* else go round for further response lines */
+ } else {
+ error(0, "%s: malformed response: %s", tag, line);
+ return -1;
+ }
+ }
+ if(ferror(in))
+ error(errno, "%s: read error", tag);
+ else
+ error(0, "%s: server closed connection", tag);
+ return -1;
+}
+
+/** @brief Send a command to the server
+ * @param tag Server name
+ * @param out stream to send commands to
+ * @param fmt Format string
+ * @return 0 on success, non-0 on error
+ */
+static int sendcommand(const char *tag, FILE *out, const char *fmt, ...) {
+ va_list ap;
+ int rc;
+
+ va_start(ap, fmt);
+ rc = vfprintf(out, fmt, ap);
+ va_end(ap);
+ if(rc >= 0)
+ rc = fputs("\r\n", out);
+ if(rc >= 0)
+ rc = fflush(out);
+ if(rc >= 0)
+ return 0;
+ error(errno, "%s: write error", tag);
+ return -1;
+}
+
+/** @brief Send a mail message
+ * @param tag Server name
+ * @param in Stream to read responses from
+ * @param out Stream to send commands to
+ * @param sender Sender address (can be "")
+ * @param pubsender Visible sender address (must not be "")
+ * @param recipient Recipient address
+ * @param subject Subject string
+ * @param encoding Body encoding
+ * @param body_type Content-type of body
+ * @param body Text of body (encoded, but \n for newline)
+ * @return 0 on success, non-0 on error
+ */
+static int sendmailfp(const char *tag, FILE *in, FILE *out,
+ const char *sender,
+ const char *pubsender,
+ const char *recipient,
+ const char *subject,
+ const char *encoding,
+ const char *content_type,
+ const char *body) {
+ int rc, sol = 1;
+ const char *ptr;
+ uint8_t idbuf[20];
+ char *id;
+
+ gcry_create_nonce(idbuf, sizeof idbuf);
+ id = mime_to_base64(idbuf, sizeof idbuf);
+ if((rc = getresponse(tag, in)) / 100 != 2)
+ return -1;
+ if(sendcommand(tag, out, "HELO %s", local_hostname()))
+ return -1;
+ if((rc = getresponse(tag, in)) / 100 != 2)
+ return -1;
+ if(sendcommand(tag, out, "MAIL FROM:<%s>", sender))
+ return -1;
+ if((rc = getresponse(tag, in)) / 100 != 2)
+ return -1;
+ if(sendcommand(tag, out, "RCPT TO:<%s>", recipient))
+ return -1;
+ if((rc = getresponse(tag, in)) / 100 != 2)
+ return -1;
+ if(sendcommand(tag, out, "DATA", sender))
+ return -1;
+ if((rc = getresponse(tag, in)) / 100 != 3)
+ return -1;
+ if(fprintf(out, "From: %s\r\n", pubsender) < 0
+ || fprintf(out, "To: %s\r\n", recipient) < 0
+ || fprintf(out, "Subject: %s\r\n", subject) < 0
+ || fprintf(out, "Message-ID: <%s@%s>\r\n", id, local_hostname()) < 0
+ || fprintf(out, "MIME-Version: 1.0\r\n") < 0
+ || fprintf(out, "Content-Type: %s\r\n", content_type) < 0
+ || fprintf(out, "Content-Transfer-Encoding: %s\r\n", encoding) < 0
+ || fprintf(out, "\r\n") < 0) {
+ write_error:
+ error(errno, "%s: write error", tag);
+ return -1;
+ }
+ for(ptr = body; *ptr; ++ptr) {
+ if(sol && *ptr == '.')
+ if(fputc('.', out) < 0)
+ goto write_error;
+ if(*ptr == '\n') {
+ if(fputc('\r', out) < 0)
+ goto write_error;
+ sol = 1;
+ } else
+ sol = 0;
+ if(fputc(*ptr, out) < 0)
+ goto write_error;
+ }
+ if(!sol)
+ if(fputs("\r\n", out) < 0)
+ goto write_error;
+ if(fprintf(out, ".\r\n") < 0
+ || fflush(out) < 0)
+ goto write_error;
+ if((rc = getresponse(tag, in)) / 100 != 2)
+ return -1;
+ return 0;
+}
+
+/** @brief Send a mail message
+ * @param sender Sender address (can be "")
+ * @param pubsender Visible sender address (must not be "")
+ * @param recipient Recipient address
+ * @param subject Subject string
+ * @param encoding Body encoding
+ * @param content_type Content-type of body
+ * @param body Text of body (encoded, but \n for newline)
+ * @return 0 on success, non-0 on error
+ *
+ * See mime_encode_text() for encoding of text bodies.
+ */
+int sendmail(const char *sender,
+ const char *pubsender,
+ const char *recipient,
+ const char *subject,
+ const char *encoding,
+ const char *content_type,
+ const char *body) {
+ struct stringlist a;
+ char *s[2];
+ struct addrinfo *ai;
+ char *tag;
+ int fdin, fdout, rc;
+ FILE *in, *out;
+
+ static const struct addrinfo pref = {
+ 0,
+ PF_INET,
+ SOCK_STREAM,
+ IPPROTO_TCP,
+ 0,
+ 0,
+ 0,
+ 0
+ };
+
+ /* Find the SMTP server */
+ a.n = 2;
+ a.s = s;
+ s[0] = config->smtp_server;
+ s[1] = (char *)"smtp";
+ if(!(ai = get_address(&a, &pref, &tag)))
+ return -1;
+ fdin = xsocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if(connect(fdin, ai->ai_addr, ai->ai_addrlen) < 0) {
+ error(errno, "error connecting to %s", tag);
+ xclose(fdin);
+ return -1;
+ }
+ if((fdout = dup(fdin)) < 0)
+ fatal(errno, "error calling dup2");
+ if(!(in = fdopen(fdin, "rb")))
+ fatal(errno, "error calling fdopen");
+ if(!(out = fdopen(fdout, "wb")))
+ fatal(errno, "error calling fdopen");
+ rc = sendmailfp(tag, in, out, sender, pubsender, recipient, subject,
+ encoding, content_type, body);
+ fclose(in);
+ fclose(out);
+ return rc;
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
--- /dev/null
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2007 Richard Kettlewell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef SENDMAIL_H
+#define SENDMAIL_H
+
+int sendmail(const char *sender,
+ const char *pubsender,
+ const char *recipient,
+ const char *subject,
+ const char *encoding,
+ const char *content_type,
+ const char *body);
+
+#endif /* SENDMAIL_H */
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
check_string(mime_qp("x =\r\ny"), "x y");
check_string(mime_qp("x = \r\ny"), "x y");
+ check_string(mime_to_qp(""), "");
+ check_string(mime_to_qp("foobar\n"), "foobar\n");
+ check_string(mime_to_qp("foobar \n"), "foobar=20\n");
+ check_string(mime_to_qp("foobar\t\n"), "foobar=09\n");
+ check_string(mime_to_qp("foobar \t \n"), "foobar=20=09=20\n");
+ check_string(mime_to_qp(" foo=bar"), " foo=3Dbar\n");
+ check_string(mime_to_qp("copyright \xC2\xA9"), "copyright =C2=A9\n");
+ check_string(mime_to_qp("foo\nbar\nbaz\n"), "foo\nbar\nbaz\n");
+ check_string(mime_to_qp("wibble wobble wibble wobble wibble wobble wibble wobble wibble wobble wibble"), "wibble wobble wibble wobble wibble wobble wibble wobble wibble wobble wibb=\nle\n");
+
/* from RFC2045 */
check_string(mime_qp("Now's the time =\r\n"
"for all folk to come=\r\n"
#include "dcgi.h"
#include "url.h"
#include "mime.h"
+#include "sendmail.h"
char *login_cookie;
static void act_register(cgi_sink *output,
dcgi_state *ds) {
const char *username, *password, *email;
- char *confirm;
+ char *confirm, *content_type;
+ const char *text, *encoding, *charset;
username = cgi_get("username");
password = cgi_get("password");
expand_template(ds, output, "login");
return;
}
+ /* Send the user a mail */
+ /* TODO templatize this */
+ byte_xasprintf((char **)&text,
+ "Welcome to DisOrder. To active your login, please visit this URL:\n"
+ "\n"
+ " %s?confirm=%s\n", config->url, confirm);
+ if(!(text = mime_encode_text(text, &charset, &encoding)))
+ fatal(0, "cannot encode email");
+ byte_xasprintf(&content_type, "text/plain;charset=%s",
+ quote822(charset, 0));
+ sendmail("", config->mail_sender, email, "Welcome to DisOrder",
+ encoding, content_type, text); /* TODO error checking */
/* We'll go back to the login page with a suitable message */
cgi_set_option("registered", "registeredok");
expand_template(ds, output, "login");
</tr>
<tr>
<td>@label:login.password@</td>
- <td><input class=password name=changepassword type=password value=""
+ <td><input class=password name=password type=password value=""
size=32></td>
<td>
<button class=register name=action type=submit value=register>