1 /* 2 * Copyright (c) 2008 The DragonFly Project. All rights reserved. 3 * 4 * This code is derived from software contributed to The DragonFly Project 5 * by Matthias Schmidt <matthias@dragonflybsd.org>, University of Marburg, 6 * Germany. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 3. Neither the name of The DragonFly Project nor the names of its 19 * contributors may be used to endorse or promote products derived 20 * from this software without specific, prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 30 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 32 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 * 35 * $DragonFly: src/libexec/dma/net.c,v 1.4 2008/02/04 10:11:41 matthias Exp $ 36 */ 37 38 #include <sys/param.h> 39 #include <sys/queue.h> 40 #include <sys/stat.h> 41 #include <sys/types.h> 42 #include <sys/socket.h> 43 #include <netinet/in.h> 44 #include <arpa/inet.h> 45 46 #ifdef HAVE_CRYPTO 47 #include <openssl/ssl.h> 48 #endif /* HAVE_CRYPTO */ 49 50 #include <netdb.h> 51 #include <setjmp.h> 52 #include <signal.h> 53 #include <syslog.h> 54 #include <unistd.h> 55 56 #include "dma.h" 57 58 extern struct config *config; 59 extern struct authusers authusers; 60 static jmp_buf timeout_alarm; 61 62 static void 63 sig_alarm(int signo __unused) 64 { 65 longjmp(timeout_alarm, 1); 66 } 67 68 ssize_t 69 send_remote_command(int fd, const char* fmt, ...) 70 { 71 va_list va; 72 char cmd[4096]; 73 ssize_t len = 0; 74 75 va_start(va, fmt); 76 vsprintf(cmd, fmt, va); 77 78 if (((config->features & SECURETRANS) != 0) && 79 ((config->features & TLSINIT) == 0)) { 80 len = SSL_write(config->ssl, (const char*)cmd, strlen(cmd)); 81 SSL_write(config->ssl, "\r\n", 2); 82 } 83 else { 84 len = write(fd, cmd, strlen(cmd)); 85 write(fd, "\r\n", 2); 86 } 87 va_end(va); 88 89 return (len+2); 90 } 91 92 static int 93 read_remote_command(int fd, char *buff) 94 { 95 ssize_t len; 96 97 if (signal(SIGALRM, sig_alarm) == SIG_ERR) { 98 syslog(LOG_ERR, "SIGALRM error: %m"); 99 } 100 if (setjmp(timeout_alarm) != 0) { 101 syslog(LOG_ERR, "Timeout reached"); 102 return (1); 103 } 104 alarm(CON_TIMEOUT); 105 106 /* 107 * According to RFC 821 a reply can consists of multiple lines, so 108 * so read until the 4th char of the reply code is != '-' 109 */ 110 if (((config->features & SECURETRANS) != 0) && 111 ((config->features & TLSINIT) == 0)) 112 do { 113 len = SSL_read(config->ssl, buff, BUF_SIZE); 114 } while (len > 3 && buff[3] == '-'); 115 else 116 do { 117 len = read(fd, buff, BUF_SIZE); 118 } while (len > 3 && buff[3] == '-'); 119 120 alarm(0); 121 122 return (0); 123 } 124 125 int 126 check_for_smtp_error(int fd, char *buff) 127 { 128 if (read_remote_command(fd, buff) < 0) 129 return (-1); 130 131 /* We received a 5XX reply thus an error happend */ 132 if (strncmp(buff, "5", 1) == 0) { 133 syslog(LOG_ERR, "SMTP error : %s", buff); 134 return (-1); 135 } 136 return (0); 137 } 138 139 /* 140 * Handle SMTP authentication 141 * 142 * XXX TODO: give me AUTH CRAM-MD5 143 */ 144 static int 145 smtp_login(struct qitem *it, int fd, char *login, char* password) 146 { 147 char buf[2048]; 148 char *temp; 149 int len; 150 151 /* Send AUTH command according to RFC 2554 */ 152 send_remote_command(fd, "AUTH LOGIN"); 153 if (check_for_smtp_error(fd, buf) < 0) { 154 syslog(LOG_ERR, "%s: remote delivery deferred:" 155 " AUTH login not available: %m", it->queueid); 156 return (1); 157 } 158 159 len = base64_encode(login, strlen(login), &temp); 160 if (len <= 0) 161 return (-1); 162 163 send_remote_command(fd, "%s", temp); 164 if (check_for_smtp_error(fd, buf) < 0) { 165 syslog(LOG_ERR, "%s: remote delivery deferred:" 166 " AUTH login failed: %m", it->queueid); 167 return (-1); 168 } 169 170 len = base64_encode(password, strlen(password), &temp); 171 if (len <= 0) 172 return (-1); 173 174 send_remote_command(fd, "%s", temp); 175 if (check_for_smtp_error(fd, buf) < 0) { 176 syslog(LOG_ERR, "%s: remote delivery deferred:" 177 " AUTH password failed: %m", it->queueid); 178 return (-1); 179 } 180 181 return (0); 182 } 183 184 static int 185 open_connection(struct qitem *it, const char *host) 186 { 187 struct addrinfo hints, *res, *res0; 188 char servname[128]; 189 const char *errmsg = NULL; 190 int fd, error = 0, port; 191 192 if (config->port != 0) 193 port = config->port; 194 else 195 port = SMTP_PORT; 196 197 /* Shamelessly taken from getaddrinfo(3) */ 198 memset(&hints, 0, sizeof(hints)); 199 hints.ai_family = PF_UNSPEC; 200 hints.ai_socktype = SOCK_STREAM; 201 hints.ai_protocol = IPPROTO_TCP; 202 203 snprintf(servname, sizeof(servname), "%d", port); 204 error = getaddrinfo(host, servname, &hints, &res0); 205 if (error) { 206 syslog(LOG_ERR, "%s: remote delivery deferred: " 207 "%s: %m", it->queueid, gai_strerror(error)); 208 return (-1); 209 } 210 fd = -1; 211 for (res = res0; res; res = res->ai_next) { 212 fd=socket(res->ai_family, res->ai_socktype, res->ai_protocol); 213 if (fd < 0) { 214 errmsg = "socket failed"; 215 continue; 216 } 217 if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { 218 errmsg = "connect failed"; 219 close(fd); 220 fd = -1; 221 continue; 222 } 223 break; 224 } 225 if (fd < 0) { 226 syslog(LOG_ERR, "%s: remote delivery deferred: %s (%s:%s)", 227 it->queueid, errmsg, host, servname); 228 freeaddrinfo(res0); 229 return (-1); 230 } 231 freeaddrinfo(res0); 232 return (fd); 233 } 234 235 int 236 deliver_remote(struct qitem *it, const char **errmsg) 237 { 238 struct authuser *a; 239 char *host, buf[2048], line[1000]; 240 int fd, error = 0, do_auth = 0; 241 size_t linelen; 242 243 host = strrchr(it->addr, '@'); 244 /* Should not happen */ 245 if (host == NULL) 246 return(-1); 247 else 248 /* Step over the @ */ 249 host++; 250 251 /* Smarthost support? */ 252 if (config->smarthost != NULL && strlen(config->smarthost) > 0) { 253 syslog(LOG_INFO, "%s: using smarthost (%s)", 254 it->queueid, config->smarthost); 255 host = config->smarthost; 256 } 257 258 fd = open_connection(it, host); 259 if (fd < 0) 260 return (1); 261 262 #ifdef HAVE_CRYPTO 263 if ((config->features & SECURETRANS) != 0) { 264 error = smtp_init_crypto(it, fd, config->features); 265 if (error >= 0) 266 syslog(LOG_INFO, "%s: SSL initialization sucessful", 267 it->queueid); 268 else 269 goto out; 270 } 271 272 /* 273 * If the user doesn't want STARTTLS, but SSL encryption, we 274 * have to enable SSL first, then send EHLO 275 */ 276 if (((config->features & STARTTLS) == 0) && 277 ((config->features & SECURETRANS) != 0)) { 278 send_remote_command(fd, "EHLO %s", hostname()); 279 if (check_for_smtp_error(fd, buf) < 0) { 280 syslog(LOG_ERR, "%s: remote delivery deferred: " 281 " EHLO failed: %m", it->queueid); 282 return (-1); 283 } 284 } 285 #endif /* HAVE_CRYPTO */ 286 if (((config->features & SECURETRANS) == 0)) { 287 send_remote_command(fd, "EHLO %s", hostname()); 288 if (check_for_smtp_error(fd, buf) < 0) { 289 syslog(LOG_ERR, "%s: remote delivery deferred: " 290 " EHLO failed: %m", it->queueid); 291 return (-1); 292 } 293 } 294 295 /* 296 * Use SMTP authentication if the user defined an entry for the remote 297 * or smarthost 298 */ 299 SLIST_FOREACH(a, &authusers, next) { 300 if (strcmp(a->host, host) == 0) { 301 do_auth = 1; 302 break; 303 } 304 } 305 306 if (do_auth == 1) { 307 /* 308 * Check if the user wants plain text login without using 309 * encryption. 310 */ 311 if (((config->features & SECURETRANS) == 0) && 312 ((config->features & INSECURE) != 0)) { 313 syslog(LOG_INFO, "%s: Use SMTP authentication", 314 it->queueid); 315 error = smtp_login(it, fd, a->login, a->password); 316 if (error < 0) { 317 syslog(LOG_ERR, "%s: remote delivery failed:" 318 " SMTP login failed: %m", it->queueid); 319 return (-1); 320 } 321 /* SMTP login is not available, so try without */ 322 else if (error > 0) 323 syslog(LOG_ERR, "%s: SMTP login not available." 324 " Try without", it->queueid); 325 } else { 326 syslog(LOG_ERR, "%s: Skip SMTP login. ", 327 it->queueid); 328 } 329 } 330 331 send_remote_command(fd, "MAIL FROM:<%s>", it->sender); 332 if (check_for_smtp_error(fd, buf) < 0) { 333 syslog(LOG_ERR, "%s: remote delivery deferred:" 334 " MAIL FROM failed: %m", it->queueid); 335 return (1); 336 } 337 338 /* XXX TODO: 339 * Iterate over all recepients and open only one connection 340 */ 341 send_remote_command(fd, "RCPT TO:<%s>", it->addr); 342 if (check_for_smtp_error(fd, buf) < 0) { 343 syslog(LOG_ERR, "%s: remote delivery deferred:" 344 " RCPT TO failed: %m", it->queueid); 345 return (1); 346 } 347 348 send_remote_command(fd, "DATA"); 349 if (check_for_smtp_error(fd, buf) < 0) { 350 syslog(LOG_ERR, "%s: remote delivery deferred:" 351 " DATA failed: %m", it->queueid); 352 return (1); 353 } 354 355 if (fseek(it->queuef, it->hdrlen, SEEK_SET) != 0) { 356 syslog(LOG_ERR, "%s: remote delivery deferred: cannot seek: %m", 357 it->queueid); 358 return (1); 359 } 360 361 while (!feof(it->queuef)) { 362 if (fgets(line, sizeof(line), it->queuef) == NULL) 363 break; 364 linelen = strlen(line); 365 if (linelen == 0 || line[linelen - 1] != '\n') { 366 syslog(LOG_CRIT, "%s: remote delivery failed:" 367 "corrupted queue file", it->queueid); 368 *errmsg = "corrupted queue file"; 369 error = -1; 370 goto out; 371 } 372 373 /* Remove trailing \n's and escape leading dots */ 374 trim_line(line); 375 376 /* 377 * If the first character is a dot, we escape it so the line 378 * length increases 379 */ 380 if (line[0] == '.') 381 linelen++; 382 383 if (send_remote_command(fd, "%s", line) != (ssize_t)linelen+1) { 384 syslog(LOG_ERR, "%s: remote delivery deferred: " 385 "write error", it->queueid); 386 error = 1; 387 goto out; 388 } 389 } 390 391 send_remote_command(fd, "."); 392 if (check_for_smtp_error(fd, buf) < 0) { 393 syslog(LOG_ERR, "%s: remote delivery deferred: %m", 394 it->queueid); 395 return (1); 396 } 397 398 send_remote_command(fd, "QUIT"); 399 if (check_for_smtp_error(fd, buf) < 0) { 400 syslog(LOG_ERR, "%s: remote delivery deferred: " 401 "QUIT failed: %m", it->queueid); 402 return (1); 403 } 404 out: 405 406 close(fd); 407 return (error); 408 } 409 410