/* * Copyright 1988 by Rayan S. Zachariassen, all rights reserved. * This will be free software, but only when it is finished. */ /* * Several extensive changes by Matti Aarnio * Copyright 1991-2003. */ /* * ZMailer SMTP server. */ #include "smtpserver.h" #define SKIPSPACE(Y) while (*Y == ' ' || *Y == '\t') ++Y #define SKIPTEXT(Y) while (*Y && *Y != ' ' && *Y != '\t') ++Y #define SKIPDIGIT(Y) while ('0' <= *Y && *Y <= '9') ++Y static int called_getbindaddr = 0; static Usockaddr bindaddr; static void dollarexpand __((unsigned char *s0, int space)); static void dollarexpand(s0, space) unsigned char *s0; int space; { unsigned char *str = s0; unsigned char *eol = s0 + space; /* assert(str < eol) */ unsigned char namebuf[80]; unsigned char *s; int len, taillen; while (*str) { if (*str != '$') { ++str; continue; } /* *str == '$' */ s0 = str; /* start position */ ++str; if (*str == '$') { /* A '$$' sequence shrinks to '$' */ strcpy((char*)str, (const char *)(str+1)); continue; } s = namebuf; if (*str == '{' || *str == '(') { int endc = (*str == '{') ? '}' : ')'; ++str; for (;*str;++str) { if (*str == endc) break; if (s < namebuf + sizeof(namebuf)-1) *s++ = *str; } if (*str) ++str; /* End char */ *s = 0; /* name end */ } else { for (;*str;++str) { if (!((isascii(*str) && isalnum(*str)) || *str == '_')) break; /* 'A'..'Z', 'a'..'z', '0'..'9', '_' */ if (s < namebuf + sizeof(namebuf)-1) *s++ = *str; } *s = 0; } if (*namebuf == 0) /* If there are e.g. "$/" or "${}" or "$()", or just "$" at the end of the line, then let it be. */ continue; s = (unsigned char*) getzenv((char*)namebuf); /* Pick whatever name there was.. */ if (!s) continue; /* No ZENV variable with this name ? */ len = strlen((char*)s); taillen = strlen((char*)str); if (len > (str - s0)) { /* Must expand the spot! */ unsigned char *replacementend = s0 + len; if ((replacementend + taillen) >= eol) { /* Grows past the buffer end, can't! */ taillen = eol - replacementend; } /* else We have space */ if (taillen > 0) { unsigned char *si = str + taillen; unsigned char *so = replacementend + taillen; /* Copy also the tail NIL ! */ for (;taillen>=0; --taillen, --so, --si) *so = *si; } if ((s0 + len) >= eol) /* The fill-in goes over the buffer end */ len = eol - s0; /* Cut down */ if (len > 0) { /* Still something can be copied ? */ memcpy(s0, s, len); str = s0 + len; } else str = s0 + (*s0 == '$'); /* Hmm.. grumble.. */ } else { /* Same space, or can shrink! */ if (len > 0) memcpy(s0, s, len); if (s0+len < str) /* Copy down */ strcpy((char*)(s0+len), (const char *)str); str = s0 + len; str[taillen] = 0; /* Chop the possible old junk from the tail */ } } eol[-1] = 0; } static void cfparam __((char *, int, const char *, int)); static void cfparam(str, size, cfgfilename, linenum) char *str; int size, linenum; const char *cfgfilename; { char *name, *param1, *param2, *param3; char *str0 = str; name = strchr(str, '\n'); /* The trailing newline chopper ... */ if (name) *name = 0; SKIPTEXT (str); /* "PARAM" */ SKIPSPACE(str); name = str; SKIPTEXT (str); if (*str != 0) *str++ = 0; if (cistrcmp(name, "help") == 0) { int i = 0, helpmax = HELPMAX; while (helplines[i] != NULL && i < helpmax) ++i; param2 = strchr(str, '\n'); if (param2) *param2 = 0; helplines[i] = strdup(str); helplines[i + 1] = NULL; /* This will always stay within the array... */ return; } if (cistrcmp(name, "hdr220") == 0) { int i = 0, hdrmax = HDR220MAX; while (hdr220lines[i] != NULL && i < hdrmax) ++i; param2 = strchr(str, '\n'); if (param2) *param2 = 0; hdr220lines[i] = strdup(str); hdr220lines[i+1] = NULL; return; } if (cistrcmp(name, "sasl-mechanisms") == 0) { param2 = strchr(str, '\n'); if (param2) *param2 = 0; SASL_Auth_Mechanisms = strdup(str); return; } /* Do '$' expansions on the string */ dollarexpand((unsigned char *)str, size - (str - str0)); SKIPSPACE(str); param1 = *str ? str : NULL; SKIPTEXT (str); if (*str != 0) *str++ = 0; SKIPSPACE(str); param2 = *str ? str : NULL; SKIPTEXT (str); if (*str != 0) *str++ = 0; SKIPSPACE(str); param3 = *str ? str : NULL; SKIPTEXT (str); if (*str != 0) *str++ = 0; /* How many parallel clients a servermode smtpserver allows running in parallel, and how many parallel sessions can be coming from same IP address */ if (cistrcmp(name, "same-ip-source-parallel-max") == 0 && param1) { sscanf(param1, "%d", &MaxSameIpSource); } else if (cistrcmp(name, "MaxSameIpSource") == 0 && param1) { sscanf(param1, "%d", &MaxSameIpSource); } else if (cistrcmp(name, "MaxParallelConnections") == 0 && param1) { sscanf(param1, "%d", &MaxParallelConnections); } else if (cistrcmp(name, "max-parallel-connections") == 0 && param1) { sscanf(param1, "%d", &MaxParallelConnections); } /* TCP related parameters */ else if (cistrcmp(name, "ListenQueueSize") == 0 && param1) { sscanf(param1, "%d", &ListenQueueSize); } else if (cistrcmp(name, "tcprcvbuffersize") == 0 && param1) { sscanf(param1, "%d", &TcpRcvBufferSize); } else if (cistrcmp(name, "tcpxmitbuffersize") == 0 && param1) { sscanf(param1, "%d", &TcpXmitBufferSize); } /* IP address and port binders */ else if (cistrcmp(name, "BindPort") == 0 && param1) { bindport = atoi(param1); if (bindport != 0 && bindport != 0xFFFFU) bindport_set = 1; } else if (cistrcmp(name, "BindAddress") == 0 && param1) { called_getbindaddr=1; if (!zgetbindaddr(param1, use_ipv6, &bindaddr)) { bindaddrs_count += 1; bindaddrs = realloc( bindaddrs, sizeof(bindaddr) * (bindaddrs_count +1) ); if (!bindaddrs) bindaddrs_count = 0; else bindaddrs[ bindaddrs_count-1 ] = bindaddr; } } /* SMTP Protocol limit & policy tune options */ else if (cistrcmp(name, "maxsize") == 0 && param1) { sscanf(param1, "%ld", &maxsize); } else if (cistrcmp(name, "min-availspace") == 0 && param1) { if (sscanf(param1, "%ld", &minimum_availspace) == 1) { /* Minimum of minimum is 1000 kB ! */ if (minimum_availspace < 1000) minimum_availspace = 1000; } } else if (cistrcmp(name, "RcptLimitCnt") == 0 && param1) { sscanf(param1, "%d", &rcptlimitcnt); if (rcptlimitcnt < 100) rcptlimitcnt = 100; } else if (cistrcmp(name, "RcptLimitCount") == 0 && param1) { sscanf(param1, "%d", &rcptlimitcnt); if (rcptlimitcnt < 100) rcptlimitcnt = 100; } else if (cistrcmp(name, "Rcpt-Limit-Count") == 0 && param1) { sscanf(param1, "%d", &rcptlimitcnt); if (rcptlimitcnt < 100) rcptlimitcnt = 100; #if 0 } else if (cistrcmp(name, "accept-percent-kludge") == 0) { percent_accept = 1; #endif } else if (cistrcmp(name, "reject-percent-kludge") == 0) { percent_accept = -1; } else if (cistrcmp(name, "allowsourceroute") == 0) { allow_source_route = 1; } else if (cistrcmp(name, "max-error-recipients") == 0 && param1) { sscanf(param1, "%d", &MaxErrorRecipients); } else if (cistrcmp(name, "max-unknown-commands") == 0 && param1) { sscanf(param1, "%d", &unknown_cmd_limit); } else if (cistrcmp(name, "sum-sizeoption-value") == 0) { sum_sizeoption_value = 1; } else if (cistrcmp(name, "use-tcp-wrapper") == 0) { use_tcpwrapper = 1; } else if (cistrcmp(name, "tarpit") == 0 && param3 /* 3 params */) { sscanf(param1,"%d",&tarpit_initial); sscanf(param2,"%d",&tarpit_exponent); sscanf(param3,"%d",&tarpit_toplimit); } else if (cistrcmp(name, "deliverby") == 0) { if (param1) deliverby_ok = atol(param1); else deliverby_ok = 0; } /* Two parameter policydb option: DBTYPE and DBPATH */ else if (cistrcmp(name, "policydb") == 0 && param2 /* 2 params */) { policydefine(&policydb, param1, param2); } else if (cistrcmp(name, "contentfilter") == 0 && param1) { if (access(param1, X_OK) == 0) contentfilter = strdup(param1); } else if (cistrcmp(name, "debug-contentfilter") == 0) { debug_content_filter = 1; } /* A few facility enablers: (default: off) */ else if (cistrcmp(name, "debugcmd") == 0) { debugcmdok = 1; } else if (cistrcmp(name, "expncmd") == 0) { expncmdok = 1; } else if (cistrcmp(name, "vrfycmd") == 0) { vrfycmdok = 1; } else if (cistrcmp(name, "enable-router") == 0) { enable_router = 1; } else if (cistrcmp(name, "smtp-auth") == 0) { auth_ok = 1; } else if (cistrcmp(name, "auth-login-also-without-tls") == 0) { auth_login_without_tls = 1; } else if (cistrcmp(name, "smtp-auth-sasl") == 0) { do_sasl = 1; } else if (cistrcmp(name, "msa-mode") == 0) { msa_mode = 1; } else if (cistrcmp(name, "smtp-auth-pipe") == 0 && param1) { smtpauth_via_pipe = strdup(param1); } /* Store various things into 'rvcdfrom' header per selectors */ else if (cistrcmp(name, "rcvd-ident") == 0) { log_rcvd_ident = 1; } else if (cistrcmp(name, "rcvd-whoson") == 0) { log_rcvd_whoson = 1; } else if (cistrcmp(name, "rcvd-auth-user") == 0) { log_rcvd_authuser = 1; } else if (cistrcmp(name, "rcvd-tls-mode") == 0) { log_rcvd_tls_mode = 1; } else if (cistrcmp(name, "rcvd-tls-peer") == 0) { log_rcvd_tls_peer = 1; } /* Some Enhanced-SMTP facility disablers: (default: on ) */ else if (cistrcmp(name, "nopipelining") == 0) { pipeliningok = 0; } else if (cistrcmp(name, "noenhancedstatuscodes") == 0) { enhancedstatusok = 0; } else if (cistrcmp(name, "noenhancedstatus") == 0) { enhancedstatusok = 0; } else if (cistrcmp(name, "no8bitmime") == 0) { mime8bitok = 0; } else if (cistrcmp(name, "nochunking") == 0) { chunkingok = 0; } else if (cistrcmp(name, "nodsn") == 0) { dsn_ok = 0; } else if (cistrcmp(name, "noehlo") == 0) { ehlo_ok = 0; } else if (cistrcmp(name, "noetrn") == 0) { etrn_ok = 0; } else if (cistrcmp(name, "no-multiline-replies") == 0) { multilinereplies = 0; } else if (cistrcmp(name, "force-rcpt-notify-never") == 0) { force_rcpt_notify_never = 1; } #ifdef HAVE_OPENSSL /* TLSv1/SSLv* options */ else if (cistrcmp(name, "use-tls") == 0) starttls_ok = 1; /* Default: OFF */ else if (cistrcmp(name, "listen-ssmtp") == 0) ssmtp_listen = 1; /* Default: OFF */ else if (cistrcmp(name, "outlook-tls-bug") == 0) { detect_incorrect_tls_use = 1; /* Default: OFF */ } else if (cistrcmp(name, "tls-cert-file") == 0 && param1) { if (tls_cert_file) free((void*)tls_cert_file); tls_cert_file = strdup(param1); if (!tls_key_file) /* default the other */ tls_key_file = strdup(param1); } else if (cistrcmp(name, "tls-key-file") == 0 && param1) { if (tls_key_file) free((void*)tls_key_file); tls_key_file = strdup(param1); if (!tls_cert_file) /* default the other */ tls_cert_file = strdup(param1); } else if (cistrcmp(name, "tls-dcert-file") == 0 && param1) { if (tls_dcert_file) free((void*)tls_dcert_file); tls_dcert_file = strdup(param1); if (!tls_dkey_file) /* default the other */ tls_dkey_file = strdup(param1); } else if (cistrcmp(name, "tls-dkey-file") == 0 && param1) { if (tls_dkey_file) free((void*)tls_dkey_file); tls_dkey_file = strdup(param1); if (!tls_dcert_file) /* default the other */ tls_dcert_file = strdup(param1); } else if (cistrcmp(name, "tls-dh1024") == 0 && param1) { if (tls_dh1024_param) free((void*)tls_dh1024_param); tls_dh1024_param = strdup(param1); } else if (cistrcmp(name, "tls-dh512") == 0 && param1) { if (tls_dh512_param) free((void*)tls_dh512_param); tls_dh512_param = strdup(param1); } else if (cistrcmp(name, "tls-random-source") == 0 && param1) { if (tls_random_source) free((void*)tls_random_source); tls_random_source = strdup(param1); } else if (cistrcmp(name, "tls-cipher-list") == 0 && param1) { if (tls_cipherlist) free((void*)tls_cipherlist); tls_cipherlist = strdup(param1); } else if (cistrcmp(name, "tls-CAfile") == 0 && param1) { if (tls_CAfile) free((void*)tls_CAfile); tls_CAfile = strdup(param1); } else if (cistrcmp(name, "tls-CApath") == 0 && param1) { if (tls_CApath) free((void*)tls_CApath); tls_CApath = strdup(param1); } else if (cistrcmp(name, "tls-loglevel") == 0 && param1) { sscanf(param1,"%d", & tls_loglevel); } else if (cistrcmp(name, "tls-enforce-tls")==0 && param1) { sscanf(param1,"%d", & tls_enforce_tls); } else if (cistrcmp(name, "tls-ccert-vd") == 0 && param1) { sscanf(param1,"%d", & tls_ccert_vd); } else if (cistrcmp(name, "tls-ask-cert") == 0 && param1) { sscanf(param1,"%d", & tls_ask_cert); } else if (cistrcmp(name, "tls-require-cert") == 0 && param1) { sscanf(param1,"%d", & tls_req_cert); } else if (cistrcmp(name, "tls-use-scache") == 0) { tls_use_scache = 1; } else if (cistrcmp(name, "tls-scache-timeout") == 0 && param1) { sscanf(param1,"%d", & tls_scache_timeout); } else if (cistrcmp(name, "lmtp-mode") == 0) { lmtp_mode = 1; } #endif /* - HAVE_OPENSSL */ /* Cluster-wide ETRN support for load-balanced smtp relay use */ else if (cistrcmp(name, "etrn-cluster") == 0 && param3 /* 3 params */) { static int idx = 0; if (idx < MAX_ETRN_CLUSTER_IDX) { etrn_cluster[idx].nodename = strdup(param1); etrn_cluster[idx].username = strdup(param2); etrn_cluster[idx].password = strdup(param3); ++idx; } } else { /* XX: report error for unrecognized PARAM keyword ?? */ type(NULL,0,NULL, "Cfgfile '%s' line %d has bad PARAM keyword/missing parameters: '%s'", cfgfilename, linenum, name); } } struct smtpconf * readcffile(name) const char *name; { FILE *fp; struct smtpconf scf, *head, *tail = NULL; char c, *cp, buf[1024], *s, *s0; int linenum = 0; if ((fp = fopen(name, "r")) == NULL) return NULL; head = NULL; buf[sizeof(buf) - 1] = 0; while (fgets(buf, sizeof buf, fp) != NULL) { ++linenum; c = buf[0]; if (c == '#' || (isascii(c) && isspace(c))) continue; if (buf[sizeof(buf) - 1] != 0 && buf[sizeof(buf) - 1] != '\n') { int cc; while ((cc = getc(fp)) != '\n' && cc != EOF); /* Scan until end-of-line */ } buf[sizeof(buf) - 1] = 0; /* Trunc, just in case.. */ cp = buf; SKIPSPACE(cp); if (strncmp(cp, "PARAM", 5) == 0) { cfparam(cp, sizeof(buf) -(cp-buf), name, linenum); continue; } scf.flags = ""; scf.next = NULL; s0 = cp; SKIPTEXT(cp); c = *cp; *cp = '\0'; s0 = strdup(s0); for (s = s0; *s; ++s) if (isascii(*s & 0xFF) && isupper(*s & 0xFF)) *s = tolower(*s & 0xFF); scf.pattern = s0; scf.maxloadavg = 999; if (c != '\0') { ++cp; SKIPSPACE(cp); if (*cp && isascii(*cp) && isdigit(*cp)) { /* Sanity-check -- 2 is VERY LOW */ if ((scf.maxloadavg = atoi(cp)) < 2) scf.maxloadavg = 2; SKIPDIGIT(cp); SKIPSPACE(cp); } scf.flags = strdup(cp); if ((cp = strchr(scf.flags, '\n')) != NULL) *cp = '\0'; } if (head == NULL) { head = tail = (struct smtpconf *) emalloc(sizeof scf); *head = scf; } else { tail->next = (struct smtpconf *) emalloc(sizeof scf); *(tail->next) = scf; tail = tail->next; } configuration_ok = 1; /* At least something! */ } fclose(fp); if (!called_getbindaddr) { bindaddr_set = !zgetbindaddr(NULL, use_ipv6, &bindaddr); if (bindaddr_set) { bindaddrs_count += 1; bindaddrs = realloc( bindaddrs, sizeof(Usockaddr) * (bindaddrs_count +1) ); if (!bindaddrs) bindaddrs_count = 0; else bindaddrs[ bindaddrs_count-1 ] = bindaddr; } } return head; } struct smtpconf * findcf(h) const char *h; { struct smtpconf *scfp; register char *cp, *s; int c; #ifndef USE_ALLOCA cp = (char*)emalloc(strlen(h) + 1); #else cp = (char*)alloca(strlen(h) + 1); #endif for (s = cp; *h != '\0'; ++h) { c = (*h) & 0xFF; if (isascii(c) && isalpha(c) && isupper(c)) *s++ = tolower(c); else *s++ = c; } *s = '\0'; for (scfp = cfhead; scfp != NULL; scfp = scfp->next) { if (strmatch(scfp->pattern, cp)) { #ifndef USE_ALLOCA free(cp); #endif return scfp; } } #ifndef USE_ALLOCA free(cp); #endif return NULL; }