Compare commits
No commits in common. "master" and "php-version-frozen" have entirely different histories.
master
...
php-versio
36 changed files with 729 additions and 814 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1 @@
|
||||||
config
|
config
|
||||||
src/config
|
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
FROM golang:1-alpine3.14 AS builder
|
FROM php:7.4-apache
|
||||||
|
|
||||||
COPY src/ /mailautoconf
|
COPY src/ /var/www/html/
|
||||||
WORKDIR /mailautoconf
|
|
||||||
RUN go build -o /mailautoconf/mailautoconf
|
|
||||||
|
|
||||||
FROM alpine:3.14
|
|
||||||
|
|
||||||
RUN apk add --no-cache bash
|
|
||||||
COPY --from=builder /mailautoconf/mailautoconf /mailautoconf/mailautoconf
|
|
||||||
COPY --from=builder /mailautoconf/default-config /mailautoconf/default-config
|
|
||||||
COPY --from=builder /mailautoconf/templates /mailautoconf/templates
|
|
||||||
|
|
||||||
COPY ./entrypoint.sh /
|
COPY ./entrypoint.sh /
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
EXPOSE 8010
|
# Use the default production configuration
|
||||||
|
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||||
|
|
||||||
|
RUN a2enmod rewrite
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
|
28
README.MD
28
README.MD
|
@ -1,12 +1,5 @@
|
||||||
# MailAutoConf - a simple, configurable autodiscover/autoconfig service for distributed and self-hosted services.
|
# MailAutoConf - a simple, configurable autodiscover/autoconfig service for distributed and self-hosted services.
|
||||||
|
|
||||||
## New GoLang version - please make sure you update your ini files to yaml!
|
|
||||||
Github : https://github.com/pswilde/mailautoconf
|
|
||||||
|
|
||||||
<a href="https://www.buymeacoffee.com/pswilde" target="_blank">
|
|
||||||
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" height="60" width="217" alt="Buy Me A Coffee" style="height: 30px !important;width: 106px !important;" >
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## What is MailAutoConf?
|
## What is MailAutoConf?
|
||||||
MailAutoConf is autodiscover/autoconfig web server for self-hosted mail services
|
MailAutoConf is autodiscover/autoconfig web server for self-hosted mail services
|
||||||
which do not have their own autodiscover service.
|
which do not have their own autodiscover service.
|
||||||
|
@ -20,14 +13,14 @@ https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat, should
|
||||||
be able to auto-configure using this service.
|
be able to auto-configure using this service.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
MailAutoConf runs its own webserver on port 8010.
|
The container file includes set up for an apache webserver to run the application.
|
||||||
You will need to supply a volume for the configuration file and port forwarding.
|
You will need to supply a volume for the configuration file and port forwarding.
|
||||||
```
|
```
|
||||||
podman run -dt \
|
podman run -dt \
|
||||||
--name mailautoconf \
|
--name mailautoconf \
|
||||||
-v ./config:/mailautoconf/config \
|
-v ./config:/var/www/html/config \
|
||||||
-p 8010:8010 \
|
-p 8010:80 \
|
||||||
pswilde/mailautoconf
|
pswilde/automailconf
|
||||||
```
|
```
|
||||||
You will need a reverse proxy server to publish to the outside world and handle SSL encryption.
|
You will need a reverse proxy server to publish to the outside world and handle SSL encryption.
|
||||||
For example, in nginx:
|
For example, in nginx:
|
||||||
|
@ -47,7 +40,7 @@ server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
First run will create sample.yaml files in the config directory. Copy these to config.yaml and services.yaml and configure them to your needs.
|
First run will create sample.ini files in the config directory. Copy these to config.ini and services.ini and configure them to your needs.
|
||||||
|
|
||||||
MailAutoConf will handle all the URLs it's able to deal with, i.e. /mail/config-v1.1.xml, /Autodiscover/Autodiscover.xml automatically.
|
MailAutoConf will handle all the URLs it's able to deal with, i.e. /mail/config-v1.1.xml, /Autodiscover/Autodiscover.xml automatically.
|
||||||
|
|
||||||
|
@ -64,13 +57,13 @@ SRV _autodiscover._tcp.your.domain 3600 10 10 443 autoconfig.your.domain
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
MailAutoConf has been tested and confirmed working (for IMAP and SMTP) with the following software packages
|
MailAutoConf has been tested and confirmed working with the following software packages
|
||||||
- [x] Thunderbird (v78 and probably earlier versions too)
|
- [x] Thunderbird (v78 and probably earlier versions too)
|
||||||
- [x] Evolution Mail (v3.40.3 and probably earlier versions too)
|
- [x] Evolution Mail (v3.40.3 and probably earlier versions too)
|
||||||
- [x] Nextcloud Mail app
|
- [x] Nextcloud Mail app
|
||||||
- [ ] Other Mail Clients are likely supported if they support /mail/config-v1.1.xml
|
- [ ] Other Mail Clients are likely supported if they support /mail/config-v1.1.xml
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Future plans
|
## Future plans
|
||||||
MailAutoConf is currently in _very_ early stages, with a _very_ limited set of features.
|
MailAutoConf is currently in _very_ early stages, with a _very_ limited set of features.
|
||||||
|
@ -90,11 +83,8 @@ Calendar and AddressBook is in the autoconfig XML documentation, but currently n
|
||||||
|
|
||||||
|
|
||||||
## When will it be ready for production?
|
## When will it be ready for production?
|
||||||
It works for non-Microsoft email clients now (see Compatibility above).
|
Well, not yet. Though it does sort of work already.
|
||||||
Outlook's autodiscover is a troublesome little blighter, MailAutoConf does generate a valid Autodiscover.xml, but modern Outlook clients use an Autodiscover.json file now which isn't documented anywhere. I'm working on this and hope to get Outlook Compatibility as soon as possible.
|
I'm working on this ultimately for my own use for my own small business. I'm hoping once it's good enough I could deploy the set up to my businesses customers and ultimately get them away from a Microsoft Exchange based environment. There's a long way to go for that right now though.
|
||||||
|
|
||||||
Then it's down to Autoconfiguration of Calendars and AddressBooks... but that's down to the email client developers really...
|
|
||||||
|
|
||||||
|
|
||||||
If you feel you may be able to help, or ideas on features and their implementation, notice any bugs, or just want to say hi. Please do so and submit a pull request if required.
|
If you feel you may be able to help, or ideas on features and their implementation, notice any bugs, or just want to say hi. Please do so and submit a pull request if required.
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ services:
|
||||||
mailautoconf:
|
mailautoconf:
|
||||||
container_name: mailautoconf
|
container_name: mailautoconf
|
||||||
ports:
|
ports:
|
||||||
- '8010:8010'
|
- '8010:80'
|
||||||
volumes:
|
volumes:
|
||||||
- './config:/mailautoconf/config'
|
- './config:/var/www/html/config'
|
||||||
image: pswilde/mailautoconf
|
image: pswilde/mailautoconf
|
||||||
|
|
|
@ -1,31 +1,29 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
echo Removing old sample files…
|
echo Removing old sample files…
|
||||||
rm /mailautoconf/config/*.sample.yaml
|
rm /var/www/html/config/*.sample.ini
|
||||||
|
|
||||||
function write_file() {
|
function write_file() {
|
||||||
while read line;
|
while read line;
|
||||||
do
|
do
|
||||||
first_char=${line:0:1}
|
first_char=${line:0:1}
|
||||||
first_vers=${line:0:7}
|
|
||||||
if [[ $first_vers == "Version" ]]; then
|
if [[ $first_char != ";" ]]; then
|
||||||
line="# This is the sample config file for MailAutoConf "$line
|
line="; "$line
|
||||||
elif [[ $first_char != "#" ]]; then
|
|
||||||
line="#"$line
|
|
||||||
fi
|
fi
|
||||||
echo $line >> $2
|
echo $line >> $2
|
||||||
done < $1
|
done < $1
|
||||||
}
|
}
|
||||||
|
|
||||||
echo Setting up new sample config files…
|
echo Setting up new sample config files…
|
||||||
def_conf="/mailautoconf/default-config/config.default.yaml"
|
def_conf="/var/www/html/default-config/config.default.ini"
|
||||||
new_conf="/mailautoconf/config/config.sample.yaml"
|
new_conf="/var/www/html/config/config.sample.ini"
|
||||||
write_file $def_conf $new_conf
|
write_file $def_conf $new_conf
|
||||||
|
|
||||||
def_serv="/mailautoconf/default-config/services.default.yaml"
|
def_serv="/var/www/html/default-config/services.default.ini"
|
||||||
new_serv="/mailautoconf/config/services.sample.yaml"
|
new_serv="/var/www/html/config/services.sample.ini"
|
||||||
write_file $def_serv $new_serv
|
write_file $def_serv $new_serv
|
||||||
|
|
||||||
echo New sample files copied
|
|
||||||
|
|
||||||
cd /mailautoconf
|
|
||||||
exec /mailautoconf/mailautoconf
|
echo Running HTTPD…
|
||||||
|
exec apache2-foreground
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
podman run --name mailautoconf \
|
podman run --name mailautoconf \
|
||||||
--rm \
|
--rm \
|
||||||
-p "8010:8010" \
|
-p "8010:80" \
|
||||||
-v ./config:/mailautoconf/config \
|
-v ./config:/var/www/html/config \
|
||||||
pswilde/mailautoconf
|
pswilde/mailautoconf
|
||||||
|
|
4
src/.htaccess
Normal file
4
src/.htaccess
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
RewriteEngine on
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^(.*)$ /index.php?path=$1 [NC,L,QSA]
|
3
src/core/db/db.php
Normal file
3
src/core/db/db.php
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<?php
|
||||||
|
echo "No Db...yet.";
|
||||||
|
?>
|
57
src/core/init.php
Normal file
57
src/core/init.php
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
// this class starts the whole thing going.
|
||||||
|
class Init {
|
||||||
|
public function start (){
|
||||||
|
// define a constant for checking later on
|
||||||
|
define("LETSGO",true);
|
||||||
|
|
||||||
|
// require the Load class which also imports the Core class
|
||||||
|
require("init/loader.php");
|
||||||
|
|
||||||
|
// Not sure if we're using a database yet, but leaving this here.
|
||||||
|
//require("db/db.php");
|
||||||
|
|
||||||
|
// Get the config
|
||||||
|
self::get_config();
|
||||||
|
|
||||||
|
// A Session may not be necessary as this is fundamentally an API
|
||||||
|
//Core::StartSession();
|
||||||
|
|
||||||
|
// Continue and load the requested page
|
||||||
|
$loader = new Loader();
|
||||||
|
$loader->request_page();
|
||||||
|
}
|
||||||
|
private function get_config(){
|
||||||
|
// parse the default config file for default values
|
||||||
|
$default_config = parse_ini_file(Core::root_dir()."/default-config/config.default.ini", true);
|
||||||
|
// define the custom config file location
|
||||||
|
$config_file = Core::root_dir()."/config/config.ini";
|
||||||
|
$config = array();
|
||||||
|
if (file_exists($config_file)) {
|
||||||
|
// if a custom config file exists then parse that file too
|
||||||
|
$config = parse_ini_file($config_file, true);
|
||||||
|
}
|
||||||
|
// merge the default config with the custom config
|
||||||
|
Core::$Config = array_merge($default_config,$config);
|
||||||
|
Core::$Config["PrimaryDomain"] = Core::$Config["Domain"][0];
|
||||||
|
|
||||||
|
// parse the default services file for default values
|
||||||
|
$default_services = parse_ini_file(Core::root_dir()."/default-config/services.default.ini", true);
|
||||||
|
|
||||||
|
// define the custom services file location
|
||||||
|
$services_file = Core::root_dir()."/config/services.ini";
|
||||||
|
$services = array();
|
||||||
|
if (file_exists($services_file)) {
|
||||||
|
// if a custom config file exists then parse that file too
|
||||||
|
$services = parse_ini_file($services_file, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge the default config with the custom config
|
||||||
|
// using replace recursive as the config file contains arrays itself
|
||||||
|
Core::$Config["Services"] = array_replace_recursive($default_services,$services);
|
||||||
|
|
||||||
|
// get the current git commit, if it exists. For testing
|
||||||
|
Core::$Config["CommitID"] = Core::get_current_git_commit();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
77
src/core/init/core.php
Normal file
77
src/core/init/core.php
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<?php class Core {
|
||||||
|
public static $Config;
|
||||||
|
public const VERSION = "0.0.7";
|
||||||
|
public static $CurrentPage;
|
||||||
|
public static function root_dir(){
|
||||||
|
return $_SERVER['DOCUMENT_ROOT'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get_post_data(){
|
||||||
|
// Gets the POST request into an array
|
||||||
|
$data = [];
|
||||||
|
foreach($_POST as $key=>$value){
|
||||||
|
$data[$key] = $value;
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
public static function get_get_data(){
|
||||||
|
// Gets the GET request into an array
|
||||||
|
$data = [];
|
||||||
|
foreach($_GET as $key=>$value){
|
||||||
|
$data[$key] = $value;
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
public static function random_string($length = 10) {
|
||||||
|
// Generates a random string - unused currently
|
||||||
|
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
$charactersLength = strlen($characters);
|
||||||
|
$randomString = '';
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$randomString .= $characters[rand(0, $charactersLength - 1)];
|
||||||
|
}
|
||||||
|
return $randomString;
|
||||||
|
}
|
||||||
|
public static function start_session(){
|
||||||
|
// Starts a session, unused currently
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
public static function end_session($redirect = true){
|
||||||
|
// Unset all of the session variables
|
||||||
|
$_SESSION = array();
|
||||||
|
setcookie("user_session", "", time() - 3600);
|
||||||
|
// Destroy the session.
|
||||||
|
session_destroy();
|
||||||
|
if($redirect){
|
||||||
|
header("location: /?e=LogoutSuccess");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static function get_current_git_commit( $branch='master' ) {
|
||||||
|
// Gets the current git commit ID - for testing
|
||||||
|
$gitref = sprintf( self::root_dir().'/../.git/refs/heads/%s', $branch );
|
||||||
|
if ( file_exists($gitref) && $hash = file_get_contents( $gitref ) ) {
|
||||||
|
return trim($hash);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static function full_url(){
|
||||||
|
// Gets the full requested URL
|
||||||
|
if(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
|
||||||
|
$addr = "https";
|
||||||
|
} else {
|
||||||
|
$addr = "http";
|
||||||
|
}
|
||||||
|
// Here append the common URL characters.
|
||||||
|
$addr .= "://";
|
||||||
|
|
||||||
|
// Append the host(domain name, ip) to the URL.
|
||||||
|
$addr .= $_SERVER['HTTP_HOST'];
|
||||||
|
|
||||||
|
// Append the requested resource location to the URL
|
||||||
|
$addr .= $_SERVER['REQUEST_URI'];
|
||||||
|
|
||||||
|
// Return the address
|
||||||
|
return $addr;
|
||||||
|
}
|
||||||
|
}
|
34
src/core/init/errors.php
Normal file
34
src/core/init/errors.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
class Errors {
|
||||||
|
public function throw_error($err){
|
||||||
|
header('Content-Type: application/json'); // <-- header declaration
|
||||||
|
$error = false;
|
||||||
|
// Some Saved error messages - more to be added
|
||||||
|
switch ($err) {
|
||||||
|
case "NotFound":
|
||||||
|
$error = new ErrorMessage("Error","404","Not Found");
|
||||||
|
break;
|
||||||
|
case "Unauthorized":
|
||||||
|
$error = new ErrorMessage("Error","402","Unauthorized");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$error = new ErrorMessage("Error","500","Internal Error");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Output error in JSON format
|
||||||
|
echo json_encode($error, true); // <--- encode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class ErrorMessage {
|
||||||
|
public $error;
|
||||||
|
public $code;
|
||||||
|
public $message;
|
||||||
|
public $commit;
|
||||||
|
public function __construct($err,$code,$msg){
|
||||||
|
$this->error = $err;
|
||||||
|
$this->code = $code;
|
||||||
|
$this->message = $msg;
|
||||||
|
$this->url = Core::$CurrentPage;
|
||||||
|
$this->commit = Core::$Config["CommitID"];
|
||||||
|
}
|
||||||
|
}
|
44
src/core/init/loader.php
Normal file
44
src/core/init/loader.php
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
// require all necessary files
|
||||||
|
require ("core.php");
|
||||||
|
require ("user.php");
|
||||||
|
require ("responder.php");
|
||||||
|
require ("errors.php");
|
||||||
|
|
||||||
|
class Loader {
|
||||||
|
public function request_page(){
|
||||||
|
// Check if user is authenticated and go to the relevant section
|
||||||
|
// Currently no authentication is in place so should always be true
|
||||||
|
if (User::is_authenticated()){
|
||||||
|
$this->go_to_page(true);
|
||||||
|
} else {
|
||||||
|
$this->go_to_page(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function go_to_page($authenticated) {
|
||||||
|
switch ($authenticated) {
|
||||||
|
case false:
|
||||||
|
require(Core::root_dir()."/public/unauthorized.php");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$p = $this->get_page_name();
|
||||||
|
if(substr($p,0,1) == "/") {
|
||||||
|
// Remove first slash if exists
|
||||||
|
Core::$CurrentPage = substr($p,1);
|
||||||
|
} else {
|
||||||
|
Core::$CurrentPage = $p;
|
||||||
|
}
|
||||||
|
require_once(Core::root_dir()."/public/respond.php");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function get_page_name(){
|
||||||
|
$uri = Core::get_get_data();
|
||||||
|
$page = "/none";
|
||||||
|
if(isset($uri["path"])){
|
||||||
|
$page = parse_url($uri["path"]);
|
||||||
|
$page = $page["path"];
|
||||||
|
}
|
||||||
|
return $page;
|
||||||
|
}
|
||||||
|
}
|
187
src/core/init/responder.php
Normal file
187
src/core/init/responder.php
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
<?php
|
||||||
|
class Responder {
|
||||||
|
private $Response;
|
||||||
|
public function show_response(){
|
||||||
|
// get the response detailed by the url requested
|
||||||
|
$response = $this->get_response();
|
||||||
|
if ($response){
|
||||||
|
// send response
|
||||||
|
$this->send_response($response);
|
||||||
|
} else {
|
||||||
|
// Send error instead
|
||||||
|
$e = new Errors();
|
||||||
|
$e->throw_error("NotFound");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function send_response($response){
|
||||||
|
switch ($response->content_type){
|
||||||
|
case "json":
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
// Send json encoded response
|
||||||
|
echo json_encode($response);
|
||||||
|
break;
|
||||||
|
case "ms-json":
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
// Send json encoded response
|
||||||
|
// This is currently an undocumented file from Microsoft so it's not ready yet
|
||||||
|
echo json_encode($response->content, JSON_UNESCAPED_UNICODE);
|
||||||
|
break;
|
||||||
|
case "xml":
|
||||||
|
header('Content-Type: application/xml');
|
||||||
|
include ($response->content);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
private function get_response(){
|
||||||
|
$resp = false;
|
||||||
|
// Handle the requested URL, using as many known autoconfiguration urls as possible
|
||||||
|
switch (strtolower(Core::$CurrentPage)){
|
||||||
|
case "get/test":
|
||||||
|
$resp = $this->dummy_response();
|
||||||
|
break;
|
||||||
|
case "get/all":
|
||||||
|
$resp = $this->all_urls();
|
||||||
|
break;
|
||||||
|
case "mail/autoconfig.xml":
|
||||||
|
case "mail/config-v1.1.xml":
|
||||||
|
$resp = $this->moz_auto_config();
|
||||||
|
break;
|
||||||
|
case "autodiscover/autodiscover.xml":
|
||||||
|
$resp = $this->ms_autodiscover();
|
||||||
|
break;
|
||||||
|
case "autodiscover/autodiscover.json":
|
||||||
|
$resp = $this->ms_autodiscover_json();
|
||||||
|
break;
|
||||||
|
case "get/config":
|
||||||
|
$resp = $this->get_config();
|
||||||
|
break;
|
||||||
|
case "none":
|
||||||
|
case "test":
|
||||||
|
case "home":
|
||||||
|
case "root":
|
||||||
|
$resp = $this->get_test_working();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return $resp;
|
||||||
|
}
|
||||||
|
private function get_config(){
|
||||||
|
$response = new Response();
|
||||||
|
$response->message = "Here's your config...";
|
||||||
|
foreach (Core::$Config as $k => $v){
|
||||||
|
$response->content[$k] = $v;
|
||||||
|
}
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
private function get_username($service,$email_address) {
|
||||||
|
$username = "%EMAILADDRESS%";
|
||||||
|
if(!$service["UsernameIsFQDN"]) {
|
||||||
|
preg_match("/^[^@]+/",$email_address,$matches);
|
||||||
|
if (count($matches) > 0){
|
||||||
|
$username = $matches[0];
|
||||||
|
}
|
||||||
|
} else if ($service["RequireLogonDomain"]) {
|
||||||
|
$username = preg_replace("/[^@]+$/",Core::$Config["LogonDomain"],$email_address,1);
|
||||||
|
}
|
||||||
|
return $username;
|
||||||
|
}
|
||||||
|
private function all_urls(){
|
||||||
|
$response = new Response();
|
||||||
|
// Not really useful, unless some lovely app developers want to parse it for their app :)
|
||||||
|
// TODO:: Will work out a better message later
|
||||||
|
$response->message = "All URLs Requested";
|
||||||
|
|
||||||
|
// Cycle through each service and add to payload
|
||||||
|
foreach (Core::$Config["Services"] as $key => $service){
|
||||||
|
if (!$service["Enabled"]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$response->content[$key] = $service;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
private function moz_auto_config(){
|
||||||
|
// The default Thunderbird or Evolution autoconfig.xml file
|
||||||
|
$response = new Response();
|
||||||
|
$response->content_type = "xml";
|
||||||
|
$response->content = "public/autoconfig.php";
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
private function ms_autodiscover(){
|
||||||
|
// The default Microsoft Autodiscover.xml file
|
||||||
|
$response = new Response();
|
||||||
|
$response->content_type = "xml";
|
||||||
|
$response->content = "public/autodiscover.php";
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
private function ms_autodiscover_json(){
|
||||||
|
// The new Microsoft Autodiscover.json file - undocumented
|
||||||
|
$response = new Response();
|
||||||
|
$response->content_type = "ms-json";
|
||||||
|
if (strtolower($_GET['Protocol']) == 'autodiscoverv1') {
|
||||||
|
$response->content = new MSAutodiscoverJSONResponse();
|
||||||
|
$response->content->Protocol = "AutodiscoverV1";
|
||||||
|
$response->content->Url = Core::$Config["BaseURL"] . "/Autodiscover/Autodiscover.xml";
|
||||||
|
} else {
|
||||||
|
$response->content = new MSAutodiscoverJSONError();
|
||||||
|
http_response_code(400);
|
||||||
|
$response->headers_set = true;
|
||||||
|
$response->content->ErrorCode = "InvalidProtocol";
|
||||||
|
$response->content->ErrorMessage = "The given protocol value '" . $_GET['Protocol'] . "' is invalid. Supported values are 'AutodiscoverV1'";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
private function dummy_response(){
|
||||||
|
// Generate a dummy response for testing
|
||||||
|
$response = new Response();
|
||||||
|
$response->message = "OK, here's some scrumptious data! Enjoy!";
|
||||||
|
$response->content = array("data" => array("some_data" => "Ohhhhhmmmm nom nom nom nom nom nom",
|
||||||
|
"extra_data" => array("garnish" => "buuuuuuuuuuurp")),
|
||||||
|
"more_data" => "yuuuuuum yum yum yum");
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
private function get_test_working(){
|
||||||
|
// Generate a dummy response for testing
|
||||||
|
$response = new Response();
|
||||||
|
$response->message = "Success! Things are working! Please request a valid URL i.e. /mail/config-v1.1.xml";
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class Response {
|
||||||
|
public $url;
|
||||||
|
public $content_type = "json";
|
||||||
|
public $message;
|
||||||
|
// public $headers_set = false;
|
||||||
|
public $content = array();
|
||||||
|
public function __construct(){
|
||||||
|
// add requested page to response. I don't know why, but it could helpful for diagnostics at some point
|
||||||
|
// Does not happen on autoconfig.xml/autodisocver.xml/autodiscover.json files
|
||||||
|
$this->url = Core::$CurrentPage;
|
||||||
|
if (!Core::$Config["CommitID"]){
|
||||||
|
$this->version = Core::VERSION;
|
||||||
|
} else {
|
||||||
|
$this->version = "git commit ".Core::$Config["CommitID"];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class AutoConfig {
|
||||||
|
public function prepare_autoconfig_info() {
|
||||||
|
// TODO: Move all the code in autoconfig.php into here
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class MSAutodiscoverJSONResponse {
|
||||||
|
// More work to do - handling of MS Autodiscover.json requests
|
||||||
|
public $Protocol;
|
||||||
|
public $Url;
|
||||||
|
}
|
||||||
|
class MSAutodiscoverJSONError {
|
||||||
|
public $ErrorCode;
|
||||||
|
public $ErrorMessage;
|
||||||
|
}
|
9
src/core/init/user.php
Normal file
9
src/core/init/user.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
class User {
|
||||||
|
public static function is_authenticated(){
|
||||||
|
// lots of work to do here to handle authentication.
|
||||||
|
// Currently just returns true for testing purposes
|
||||||
|
// I intend to make this authenticate against the primary IMAP server
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
13
src/default-config/config.default.ini
Normal file
13
src/default-config/config.default.ini
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
; Sample config.ini file.
|
||||||
|
; Copy this file to "config/config.ini" and adjust the settings to your requirements
|
||||||
|
|
||||||
|
; The URL of this application
|
||||||
|
BaseURL = "https://autoconfig.example.com"
|
||||||
|
|
||||||
|
; Set the email domains for use with this service. The first one is primary.
|
||||||
|
; each will need their own DNS A record for autoconfig.domain.name
|
||||||
|
Domain[] = example.com
|
||||||
|
Domain[] = example2.com
|
||||||
|
|
||||||
|
; If you use a different domain to authenticate with, enter it here
|
||||||
|
LogonDomain = example.local
|
|
@ -1,17 +0,0 @@
|
||||||
---
|
|
||||||
Version : "0.1.5"
|
|
||||||
# Sample config.yaml file.
|
|
||||||
# Copy this file to "config/config.yaml" and adjust the
|
|
||||||
# settings to your requirements
|
|
||||||
|
|
||||||
# The URL of this application
|
|
||||||
BaseURL : "https://autoconfig.example.com"
|
|
||||||
|
|
||||||
# Set the email domains for use with this service. The first one is primary.
|
|
||||||
# each will need their own DNS A record for autoconfig.domain.name
|
|
||||||
Domains :
|
|
||||||
- "example.com"
|
|
||||||
- "example2,com"
|
|
||||||
|
|
||||||
# If you use a different domain to authenticate with, enter it here
|
|
||||||
LocalDomain : "example.local"
|
|
116
src/default-config/services.default.ini
Normal file
116
src/default-config/services.default.ini
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
; The Incoming mail Server Config
|
||||||
|
[InMail]
|
||||||
|
|
||||||
|
; Enable this service
|
||||||
|
Enabled = true
|
||||||
|
|
||||||
|
; Mail Type, i.e. IMAP, POP3
|
||||||
|
Type = "IMAP"
|
||||||
|
|
||||||
|
; Your IMAP server
|
||||||
|
Server = "imap.example.com"
|
||||||
|
|
||||||
|
; Your IMAP port
|
||||||
|
Port = 993
|
||||||
|
|
||||||
|
; The socket type : SSL, STARTTLS, or NONE
|
||||||
|
SocketType = SSL
|
||||||
|
|
||||||
|
; Use Secure Password Authentication
|
||||||
|
SPA = false
|
||||||
|
|
||||||
|
; Change to true if you need the domain/logondomain to form part of the username
|
||||||
|
UsernameIsFQDN = false
|
||||||
|
|
||||||
|
; Do you need to authenticate to your mail server? You should! so this should be false!
|
||||||
|
NoAuthRequired = false
|
||||||
|
|
||||||
|
; Authentication type,
|
||||||
|
;"password-cleartext" : Send password in the clear
|
||||||
|
; (dangerous, if SSL isn't used either).
|
||||||
|
; AUTH PLAIN, LOGIN or protocol-native login.
|
||||||
|
;"password-encrypted" : A secure encrypted password mechanism.
|
||||||
|
; Can be CRAM-MD5 or DIGEST-MD5. Not NTLM.
|
||||||
|
;"NTLM": Use NTLM (or NTLMv2 or successors),
|
||||||
|
; the Windows login mechanism.
|
||||||
|
Authentication = password-cleartext
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; The Outgoing mail server config
|
||||||
|
[OutMail]
|
||||||
|
|
||||||
|
; Enable this service
|
||||||
|
Enabled = true
|
||||||
|
|
||||||
|
; Mail type, likely to only be SMTP
|
||||||
|
Type = "SMTP"
|
||||||
|
|
||||||
|
; Your SMTP server
|
||||||
|
Server = "smtp.example.com"
|
||||||
|
|
||||||
|
; Your SMTP port
|
||||||
|
Port = 465
|
||||||
|
|
||||||
|
; The socket type : SSL, STARTTLS or NONE
|
||||||
|
SocketType = SSL
|
||||||
|
|
||||||
|
; See InMail > Authentication
|
||||||
|
Authentication = password-cleartext
|
||||||
|
|
||||||
|
; Use Secure Password Authentication
|
||||||
|
SPA = false
|
||||||
|
|
||||||
|
; Change to true if you need the domain/logondomain to form part of the username
|
||||||
|
UsernameIsFQDN = false
|
||||||
|
|
||||||
|
; Do you need to authenticate to your mail server? You should! so this should be false!
|
||||||
|
NoAuthRequired = false
|
||||||
|
|
||||||
|
; Use POP Authentication? You probably shouldn't be.
|
||||||
|
POPAuth = false
|
||||||
|
|
||||||
|
; This setting is here to limit errors, I'm not sure what it does yet.
|
||||||
|
SMTPLast = false
|
||||||
|
|
||||||
|
|
||||||
|
; Currently not implemented, see https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
|
||||||
|
[Calendar]
|
||||||
|
; Disable this service
|
||||||
|
Enabled = false
|
||||||
|
Server = "https://example.com/remote.php/dav/"
|
||||||
|
Port = 443
|
||||||
|
Type = CalDAV
|
||||||
|
Authentication = http-basic
|
||||||
|
UsernameIsFQDN = false
|
||||||
|
|
||||||
|
; Currently not implemented, see https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
|
||||||
|
[AddressBook]
|
||||||
|
; Disable this service
|
||||||
|
Enabled = false
|
||||||
|
Server = "https://example.com/remote.php/dav/"
|
||||||
|
Port = 443
|
||||||
|
Type = CardDAV
|
||||||
|
Authentication = http-basic
|
||||||
|
UsernameIsFQDN = false
|
||||||
|
|
||||||
|
; Currently not implemented, see https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
|
||||||
|
[WebMail]
|
||||||
|
; Disable this service
|
||||||
|
Enabled = false
|
||||||
|
Server = https://mail.example.com
|
||||||
|
UsernameDivID = "username"
|
||||||
|
UsernameDivName = "username"
|
||||||
|
PasswordDivName = "password"
|
||||||
|
SubmitButtonID = "submit"
|
||||||
|
SubmitButtonName = "submit"
|
||||||
|
UsernameIsFQDN = false
|
||||||
|
|
||||||
|
|
||||||
|
; In theory, additional custom services can be configured and will be displayed with
|
||||||
|
; their options on the /get/all URL of this service. The third-party clients would need to
|
||||||
|
; check this service as part of their development for this to work
|
||||||
|
; Will not be shown in autodiscover.xml/json or config-v1.1.xml/autoconfig.xml
|
||||||
|
; i.e Nextcloud - ideally a nextcloud client could check autoconfig for this URL for ease of set up
|
||||||
|
;[Nextcloud]
|
||||||
|
;Server = https://nextcloud.example.com
|
|
@ -1,133 +0,0 @@
|
||||||
# Sample services.yaml file.
|
|
||||||
# Copy this file to "config/services.yaml" and adjust the
|
|
||||||
# settings to your requirements
|
|
||||||
---
|
|
||||||
|
|
||||||
# The Incoming mail Server Config
|
|
||||||
InMail:
|
|
||||||
Name: "InMail"
|
|
||||||
|
|
||||||
# Enable this service
|
|
||||||
Enabled: true
|
|
||||||
|
|
||||||
# Mail Type, i.e. IMAP, POP3
|
|
||||||
Type: "IMAP"
|
|
||||||
|
|
||||||
# Your IMAP server
|
|
||||||
Server: "imap.example.com"
|
|
||||||
|
|
||||||
# Your IMAP port
|
|
||||||
Port: 993
|
|
||||||
|
|
||||||
# The socket type : SSL, STARTTLS, or NONE
|
|
||||||
SocketType: "SSL"
|
|
||||||
|
|
||||||
# Use Secure Password Authentication
|
|
||||||
SPA: false
|
|
||||||
|
|
||||||
# Change to true if you need the domain/logondomain to form part of the username
|
|
||||||
UsernameIsFQDN: false
|
|
||||||
|
|
||||||
# Use the LogonDomain instead of the Email Domain
|
|
||||||
RequireLocalDomain : false
|
|
||||||
|
|
||||||
# Do you need to authenticate to your mail server? You should! so this should be false!
|
|
||||||
NoAuthRequired: false
|
|
||||||
|
|
||||||
# Authentication type,
|
|
||||||
#"password-cleartext" : Send password in the clear
|
|
||||||
# (dangerous, if SSL isn't used either).
|
|
||||||
# AUTH PLAIN, LOGIN or protocol-native login.
|
|
||||||
#"password-encrypted" : A secure encrypted password mechanism.
|
|
||||||
# Can be CRAM-MD5 or DIGEST-MD5. Not NTLM.
|
|
||||||
#"NTLM": Use NTLM (or NTLMv2 or successors),
|
|
||||||
# the Windows login mechanism.
|
|
||||||
Authentication: "password-cleartext"
|
|
||||||
|
|
||||||
# The Outgoing mail server config
|
|
||||||
OutMail:
|
|
||||||
# Enable this service
|
|
||||||
Enabled: true
|
|
||||||
|
|
||||||
# Mail type, likely to only be SMTP
|
|
||||||
Type: "SMTP"
|
|
||||||
|
|
||||||
# Your SMTP server
|
|
||||||
Server: "smtp.example.com"
|
|
||||||
|
|
||||||
# Your SMTP port
|
|
||||||
Port: 465
|
|
||||||
|
|
||||||
# The socket type : SSL, STARTTLS or NONE
|
|
||||||
SocketType: "SSL"
|
|
||||||
|
|
||||||
# See InMail > Authentication
|
|
||||||
Authentication: "password-cleartext"
|
|
||||||
|
|
||||||
# Use Secure Password Authentication
|
|
||||||
SPA: false
|
|
||||||
|
|
||||||
# Change to true if you need the domain/logondomain to form part of the username
|
|
||||||
UsernameIsFQDN: false
|
|
||||||
|
|
||||||
# Use the LogonDomain instead of the Email Domain
|
|
||||||
RequireLocalDomain : false
|
|
||||||
|
|
||||||
# Do you need to authenticate to your mail server? You should! so this should be false!
|
|
||||||
NoAuthRequired: false
|
|
||||||
|
|
||||||
# Use POP Authentication? You probably shouldn't be.
|
|
||||||
POPAuth: false
|
|
||||||
|
|
||||||
# This setting is here to limit errors, I'm not sure what it does yet.
|
|
||||||
SMTPLast: false
|
|
||||||
|
|
||||||
|
|
||||||
# Currently not implemented, see https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
|
|
||||||
Calendar:
|
|
||||||
Name: "Calendar"
|
|
||||||
# Disable this service
|
|
||||||
Enabled: false
|
|
||||||
Server: "https://example.com/remote.php/dav/"
|
|
||||||
Port: 443
|
|
||||||
Type: "CalDAV"
|
|
||||||
Authentication: "http-basic"
|
|
||||||
UsernameIsFQDN: false
|
|
||||||
RequireLocalDomain : false
|
|
||||||
|
|
||||||
# Currently not implemented, see https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
|
|
||||||
AddressBook:
|
|
||||||
Name: "AddressBook"
|
|
||||||
# Disable this service
|
|
||||||
Enabled: false
|
|
||||||
Server: "https://example.com/remote.php/dav/"
|
|
||||||
Port: 443
|
|
||||||
Type: "CardDAV"
|
|
||||||
Authentication: "http-basic"
|
|
||||||
UsernameIsFQDN: false
|
|
||||||
RequireLocalDomain : false
|
|
||||||
|
|
||||||
# Currently not implemented, see https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
|
|
||||||
WebMail:
|
|
||||||
Name: "WebMail"
|
|
||||||
# Disable this service
|
|
||||||
Enabled: false
|
|
||||||
Server: "https://mail.example.com"
|
|
||||||
UsernameDivID: "username"
|
|
||||||
UsernameDivName: "username"
|
|
||||||
PasswordDivName: "password"
|
|
||||||
SubmitButtonID: "submit"
|
|
||||||
SubmitButtonName: "submit"
|
|
||||||
UsernameIsFQDN: false
|
|
||||||
RequireLocalDomain : false
|
|
||||||
|
|
||||||
|
|
||||||
# In theory, additional custom services can be configured and will be displayed with
|
|
||||||
# their options on the /get/all URL of this service. The third-party clients would need to
|
|
||||||
# check this service as part of their development for this to work
|
|
||||||
# Will not be shown in autodiscover.xml/json or config-v1.1.xml/autoconfig.xml
|
|
||||||
# i.e Nextcloud - ideally a nextcloud client could check autoconfig for this URL for ease of set up
|
|
||||||
#OtherServices:
|
|
||||||
# -
|
|
||||||
# Name : "NextCloud"
|
|
||||||
# Server : "https://nextcloud.example.com"
|
|
|
@ -1,157 +0,0 @@
|
||||||
package global
|
|
||||||
import (
|
|
||||||
. "mailautoconf/global/structs"
|
|
||||||
"mailautoconf/global/logger"
|
|
||||||
"fmt"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"io/ioutil"
|
|
||||||
// "os"
|
|
||||||
"encoding/json"
|
|
||||||
"text/template"
|
|
||||||
"path"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
// "reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Global variables
|
|
||||||
var ThisSession Session
|
|
||||||
var MainConfig Config
|
|
||||||
|
|
||||||
// Template declaration
|
|
||||||
var templates []string = []string{"autodiscover.xml","autoconfig.xml"}
|
|
||||||
var Templates map[string]*template.Template = make(map[string]*template.Template)
|
|
||||||
|
|
||||||
const defaultConfigDir string = "default-config/"
|
|
||||||
const configDir string = "config/"
|
|
||||||
|
|
||||||
func NewSessionID() string{
|
|
||||||
timecode := time.Now()
|
|
||||||
id := timecode.Format("20060102150405.000")
|
|
||||||
id = strings.Replace(id,".","",1)
|
|
||||||
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
func NewConfig() Config {
|
|
||||||
MainConfig = loadConfig()
|
|
||||||
loadXMLTemplates()
|
|
||||||
return MainConfig
|
|
||||||
}
|
|
||||||
func loadConfig() Config {
|
|
||||||
cfg := Config{}
|
|
||||||
logger.Log("Loading Default Config…")
|
|
||||||
cfgfile := defaultConfigDir + "config.default.yaml"
|
|
||||||
unmarshalConfig(cfgfile, &cfg)
|
|
||||||
logger.Log("Loading Custom Config…")
|
|
||||||
customcfgfile := configDir + "config.yaml"
|
|
||||||
unmarshalConfig(customcfgfile, &cfg)
|
|
||||||
logger.Log("Loading Default Services…")
|
|
||||||
svcfile := defaultConfigDir + "services.default.yaml"
|
|
||||||
unmarshalConfig(svcfile, &cfg)
|
|
||||||
logger.Log("Loading Custom Services…")
|
|
||||||
customsvcfile := configDir + "services.yaml"
|
|
||||||
unmarshalConfig(customsvcfile, &cfg)
|
|
||||||
removeDisabledItems(&cfg)
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
func loadXMLTemplates(){
|
|
||||||
for _, tmpl := range templates {
|
|
||||||
tmpl := fmt.Sprintf("templates/%s",tmpl)
|
|
||||||
name := path.Base(tmpl)
|
|
||||||
var fmap = template.FuncMap{
|
|
||||||
"lower": strings.ToLower,
|
|
||||||
"parseUsername": parseUsername,
|
|
||||||
"onoff": chooseOnOff,
|
|
||||||
}
|
|
||||||
t, err := template.New(name).Funcs(fmap).ParseFiles(tmpl)
|
|
||||||
logger.CheckError(err)
|
|
||||||
Templates[name] = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func unmarshalConfig(file string, cfg *Config) {
|
|
||||||
if logger.FileExists(file) {
|
|
||||||
content, err := ioutil.ReadFile(file)
|
|
||||||
if !logger.ErrorOK(err){
|
|
||||||
logger.Log("Error reading config :", file, " : ", fmt.Sprintf("%v",err))
|
|
||||||
}
|
|
||||||
err2 := yaml.Unmarshal(content, &cfg)
|
|
||||||
if !logger.ErrorOK(err2){
|
|
||||||
logger.Log("Error unmarshaling config :", file, " : ", fmt.Sprintf("%v",err2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeDisabledItems(cfg *Config) {
|
|
||||||
// Rework this, not pretty
|
|
||||||
if !cfg.InMail.Enabled {
|
|
||||||
cfg.InMail = Service{}
|
|
||||||
}
|
|
||||||
if !cfg.OutMail.Enabled {
|
|
||||||
cfg.OutMail = Service{}
|
|
||||||
}
|
|
||||||
if !cfg.Calendar.Enabled {
|
|
||||||
cfg.Calendar = Service{}
|
|
||||||
}
|
|
||||||
if !cfg.AddressBook.Enabled {
|
|
||||||
cfg.AddressBook = Service{}
|
|
||||||
}
|
|
||||||
if !cfg.WebMail.Enabled {
|
|
||||||
cfg.WebMail = Service{}
|
|
||||||
}
|
|
||||||
new_svcs := []Service{}
|
|
||||||
for _,svc := range cfg.OtherServices {
|
|
||||||
if svc.Enabled {
|
|
||||||
new_svcs = append(new_svcs,svc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cfg.OtherServices = new_svcs
|
|
||||||
}
|
|
||||||
|
|
||||||
func JSONifyConfig(content Config) string {
|
|
||||||
data, err := json.Marshal(content)
|
|
||||||
logger.CheckError(err)
|
|
||||||
return string(data)
|
|
||||||
}
|
|
||||||
func JSONify(content interface{}) string {
|
|
||||||
data, err := json.Marshal(content)
|
|
||||||
logger.CheckError(err)
|
|
||||||
return string(data)
|
|
||||||
}
|
|
||||||
func parseUsername(svc Service, email string) string {
|
|
||||||
if email == "" {
|
|
||||||
return "not-provided"
|
|
||||||
}
|
|
||||||
if svc.UsernameIsFQDN && !svc.RequireLocalDomain{
|
|
||||||
return email
|
|
||||||
} else if svc.UsernameIsFQDN && svc.RequireLocalDomain {
|
|
||||||
re := regexp.MustCompile(`[^@(%40)]+$`)
|
|
||||||
domain := re.FindString(email)
|
|
||||||
localemail := strings.Replace(email, domain,
|
|
||||||
MainConfig.LocalDomain,1)
|
|
||||||
return localemail
|
|
||||||
} else {
|
|
||||||
re := regexp.MustCompile(`^[^@(%40)]+`)
|
|
||||||
username := re.FindString(email)
|
|
||||||
return username
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func chooseOnOff(value bool) string {
|
|
||||||
if value {
|
|
||||||
return "on"
|
|
||||||
} else {
|
|
||||||
return "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// GetIP gets a requests IP address by reading off the forwarded-for
|
|
||||||
// header (for proxies) and falls back to use the remote address.
|
|
||||||
func GetSessionIP() string {
|
|
||||||
r := ThisSession.Request
|
|
||||||
ip := r.RemoteAddr
|
|
||||||
forwarded := r.Header.Get("X-FORWARDED-FOR")
|
|
||||||
if forwarded != "" {
|
|
||||||
ip = forwarded
|
|
||||||
}
|
|
||||||
logger.Log("Session ", ThisSession.ID, " Connect From : ", ip)
|
|
||||||
return ip
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
const logDir = "config/logs"
|
|
||||||
func Log(str ...string) {
|
|
||||||
makeLogDir()
|
|
||||||
|
|
||||||
line := ""
|
|
||||||
for _, s := range str {
|
|
||||||
line = line + s
|
|
||||||
}
|
|
||||||
line = line + "\r\n"
|
|
||||||
log.Print(line)
|
|
||||||
t := time.Now()
|
|
||||||
logname := fmt.Sprintf("%s_log.log",t.Format("200601"))
|
|
||||||
logfile := fmt.Sprintf("%s/%s",logDir, logname)
|
|
||||||
line = fmt.Sprintf("%s %s",t.Format("2006/01/02 15:04:05"), line)
|
|
||||||
if !FileExists(logfile) {
|
|
||||||
err := ioutil.WriteFile(logfile, []byte(line), 0755)
|
|
||||||
CheckError(err)
|
|
||||||
} else {
|
|
||||||
file, err := os.OpenFile(logfile, os.O_APPEND|os.O_WRONLY, 0755)
|
|
||||||
CheckError(err)
|
|
||||||
defer file.Close()
|
|
||||||
if _, err := file.WriteString(line); err != nil {
|
|
||||||
CheckError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
func CheckError(err error) (ok bool) {
|
|
||||||
// here for obsolescence
|
|
||||||
return ErrorOK(err)
|
|
||||||
}
|
|
||||||
func ErrorOK(err error) (ok bool) {
|
|
||||||
ok = true // All is OK, err == nil
|
|
||||||
if err != nil {
|
|
||||||
ok = false // There's an error, print it
|
|
||||||
e := fmt.Sprintf("%v",err)
|
|
||||||
Log(e)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
func Fatal(err error) {
|
|
||||||
e := fmt.Sprintf("%v",err)
|
|
||||||
Log(e)
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
func makeLogDir(){
|
|
||||||
_, err := os.Stat(logDir)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
os.Mkdir(logDir, 0755)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func FileExists(file string) bool {
|
|
||||||
exists := false
|
|
||||||
_, err := os.Stat(file);
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
log.Print("File does not exist : ", file);
|
|
||||||
} else if err == nil {
|
|
||||||
exists = true
|
|
||||||
} else {
|
|
||||||
log.Fatal(err)
|
|
||||||
log.Print("File %s does not exist\n", file);
|
|
||||||
}
|
|
||||||
return exists
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
package structs
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
|
|
||||||
type Session struct {
|
|
||||||
ID string
|
|
||||||
IP string
|
|
||||||
ResponseWriter http.ResponseWriter
|
|
||||||
Request *http.Request
|
|
||||||
Path string
|
|
||||||
WebContent string
|
|
||||||
ContentType string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Version string `yaml:"Version"`
|
|
||||||
BaseURL string `yaml:"BaseURL"`
|
|
||||||
Domains []string `yaml:"Domains"`
|
|
||||||
LocalDomain string `yaml:"LocalDomain"`
|
|
||||||
InMail Service `yaml:"InMail" json:",omitempty"`
|
|
||||||
OutMail Service `yaml:"OutMail" json:",omitempty"`
|
|
||||||
Calendar Service `yaml:"Calendar" json:",omitempty"`
|
|
||||||
AddressBook Service `yaml:"AddressBook" json:",omitempty"`
|
|
||||||
WebMail Service `yaml:"WebMail" json:",omitempty"`
|
|
||||||
OtherServices []Service `yaml:"OtherServices" json:",omitempty"`
|
|
||||||
|
|
||||||
}
|
|
||||||
type Service struct {
|
|
||||||
Name string `yaml:"Name" json:",omitempty"`
|
|
||||||
Enabled bool `yaml:"Enabled" json:",omitempty"`
|
|
||||||
Type string `yaml:"Type" json:",omitempty"`
|
|
||||||
Server string `yaml:"Server" json:",omitempty"`
|
|
||||||
Port int `yaml:"Port" json:",omitempty"`
|
|
||||||
SocketType string `yaml:"SocketType" json:",omitempty"`
|
|
||||||
SPA bool `yaml:"SPA" json:",omitempty"`
|
|
||||||
UsernameIsFQDN bool `yaml:"UsernameIsFQDN" json:",omitempty"`
|
|
||||||
RequireLocalDomain bool `yaml:"RequireLocalDomain" json:",omitempty"`
|
|
||||||
NoAuthRequired bool `yaml:"NoAuthRequired" json:",omitempty"`
|
|
||||||
Authentication string `yaml:"Authentication" json:",omitempty"`
|
|
||||||
// For Outgoing Mail
|
|
||||||
POPAuth bool `yaml:"POPAuth" json:",omitempty"`
|
|
||||||
SMTPLast bool `yaml:"SMTPLast" json:",omitempty"`
|
|
||||||
// For WebMail (Unused)
|
|
||||||
UsernameDivID string `yaml:"UsernameDivID" json:",omitempty"`
|
|
||||||
UsernameDivName string `yaml:"UsernameDivName" json:",omitempty"`
|
|
||||||
PasswordDivName string `yaml:"PasswordDivName" json:",omitempty"`
|
|
||||||
SubmitButtonID string `yaml:"SubmitButtonID" json:",omitempty"`
|
|
||||||
SubmitButtonName string `yaml:"SubmitButtonName" json:",omitempty"`
|
|
||||||
}
|
|
||||||
type Response struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
ContentType string `json:"content_type"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Content map[string]interface{} `json:"content,omitempty"`
|
|
||||||
Config Config `json:"_"`
|
|
||||||
Email string `json:"_"`
|
|
||||||
}
|
|
||||||
type MSAutodiscoverJSONResponse struct {
|
|
||||||
// More work to do - handling of MS Autodiscover.json requests
|
|
||||||
Protocol string
|
|
||||||
Url string
|
|
||||||
}
|
|
||||||
type MSAutodiscoverJSONError struct{
|
|
||||||
ErrorCode string
|
|
||||||
ErrorMessage string
|
|
||||||
}
|
|
15
src/go.mod
15
src/go.mod
|
@ -1,15 +0,0 @@
|
||||||
module mailautoconf
|
|
||||||
|
|
||||||
go 1.16
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
|
||||||
github.com/Masterminds/semver v1.5.0 // indirect
|
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
|
||||||
github.com/huandu/xstrings v1.3.2 // indirect
|
|
||||||
github.com/imdario/mergo v0.3.12 // indirect
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
)
|
|
28
src/go.sum
28
src/go.sum
|
@ -1,28 +0,0 @@
|
||||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
|
||||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
|
||||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
|
||||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
|
||||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
|
||||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
|
||||||
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ=
|
|
||||||
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
5
src/index.php
Normal file
5
src/index.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
// entry point - inport and run the public index.php file
|
||||||
|
include("core/init.php");
|
||||||
|
$b = new Init();
|
||||||
|
$b->start();
|
|
@ -1,17 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
// "fmt"
|
|
||||||
"net/http"
|
|
||||||
// "log"
|
|
||||||
"mailautoconf/web/handler"
|
|
||||||
"mailautoconf/global"
|
|
||||||
"mailautoconf/global/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
global.NewConfig()
|
|
||||||
http.HandleFunc("/", handler.WebHandler)
|
|
||||||
logger.Log("Starting up Web Listener on port 8010")
|
|
||||||
logger.Fatal(http.ListenAndServe(":8010", nil))
|
|
||||||
}
|
|
66
src/public/autoconfig.php
Normal file
66
src/public/autoconfig.php
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
// Get some core information to help with generation.
|
||||||
|
$services = Core::$Config["Services"];
|
||||||
|
$data = Core::get_get_data();
|
||||||
|
if (isset($data["emailaddress"])) {
|
||||||
|
$email_address = $data["emailaddress"];
|
||||||
|
}
|
||||||
|
// The below link has config-v1.1.xml information
|
||||||
|
// https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
|
||||||
|
?>
|
||||||
|
<clientConfig version="1.1">
|
||||||
|
<emailProvider id="<?php echo Core::$Config["PrimaryDomain"]?>">
|
||||||
|
<?php foreach (Core::$Config["Domain"] as $domain){ ?>
|
||||||
|
<domain><?php echo $domain; ?></domain>
|
||||||
|
<?php } ?>
|
||||||
|
<displayName>%EMAILADDRESS%</displayName>
|
||||||
|
<?php if($services["InMail"]&& $services["InMail"]["Enabled"]){
|
||||||
|
$service = $services["InMail"]; ?>
|
||||||
|
<incomingServer type="<?php echo strtolower($service["Type"]);?>">
|
||||||
|
<hostname><?php echo $service["Server"];?></hostname>
|
||||||
|
<port><?php echo $service["Port"];?></port>
|
||||||
|
<socketType><?php echo $service["SocketType"];?></socketType>
|
||||||
|
<username><?php echo $this->get_username($service,$email_address); ?></username>
|
||||||
|
<authentication><?php echo $service["Authentication"];?></authentication>
|
||||||
|
</incomingServer>
|
||||||
|
<?php }
|
||||||
|
if($services["OutMail"]&& $services["OutMail"]["Enabled"]){
|
||||||
|
$service = $services["OutMail"]; ?>
|
||||||
|
<outgoingServer type="<?php echo strtolower($service["Type"]);?>">
|
||||||
|
<hostname><?php echo $service["Server"];?></hostname>
|
||||||
|
<port><?php echo $service["Port"];?></port>
|
||||||
|
<socketType><?php echo $service["SocketType"];?></socketType>
|
||||||
|
<username><?php echo $this->get_username($service,$email_address);?></username>
|
||||||
|
<authentication><?php echo $service["Authentication"];?></authentication>
|
||||||
|
</outgoingServer>
|
||||||
|
<?php }
|
||||||
|
if ($services["AddressBook"] && $services["AddressBook"]["Enabled"]) {
|
||||||
|
$service = $services["AddressBook"]; ?>
|
||||||
|
<addressBook type="<?php echo strtolower($service["Type"]); ?>">
|
||||||
|
<username><?php echo $this->get_username($service,$email_address);?></username>
|
||||||
|
<authentication><?php echo $service["Authentication"];?></authentication>
|
||||||
|
<serverURL><?php echo $service["Server"];?></serverURL>
|
||||||
|
</addressBook>
|
||||||
|
<?php }
|
||||||
|
if ($services["Calendar"] && $services["Calendar"]["Enabled"]){
|
||||||
|
$service = $services["Calendar"] ;?>
|
||||||
|
<calendar type="<?php echo strtolower($service["Type"]);?>">
|
||||||
|
<username><?php echo $this->get_username($service,$email_address);?></username>
|
||||||
|
<authentication><?php echo $service["Authentication"];?></authentication>
|
||||||
|
<serverURL><?php echo $service["Server"];?></serverURL>
|
||||||
|
</calendar>
|
||||||
|
<?php }
|
||||||
|
if ($services["WebMail"] && $services["WebMail"]["Enabled"]) {
|
||||||
|
$service = $services["WebMail"]; ?>
|
||||||
|
<webMail>
|
||||||
|
<loginPage url="<?php echo $service["Server"];?>" />
|
||||||
|
<loginPageInfo url="<?php echo $service["Server"];?>">
|
||||||
|
<username><?php echo $this->get_username($service,$email_address);?></username>
|
||||||
|
<usernameField id="<?php echo $service["UsernameDivID"];?>" name="<?php echo $service["UsernameDivName"];?>" />
|
||||||
|
<passwordField name="<?php echo $service["PasswordDivName"];?>" />
|
||||||
|
<loginButton id="<?php echo $service["SubmitButtonID"];?>" name="<?php echo $service["SubmitButtonName"];?>"/>
|
||||||
|
</loginPageInfo>
|
||||||
|
</webMail>
|
||||||
|
<?php } ?>
|
||||||
|
</emailProvider>
|
||||||
|
</clientConfig>
|
52
src/public/autodiscover.php
Normal file
52
src/public/autodiscover.php
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
$conf = Core::$Config["Services"];
|
||||||
|
//get raw POST data so we can extract the email address
|
||||||
|
$data = file_get_contents("php://input");
|
||||||
|
preg_match("/\<EMailAddress\>(.*?)\<\/EMailAddress\>/", $data, $matches);
|
||||||
|
|
||||||
|
// Example POST Request (sent from client) :
|
||||||
|
// <?xml version="1.0" \?\>
|
||||||
|
// <Autodiscover xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
|
||||||
|
// <Request>
|
||||||
|
// <EMailAddress>your@email.address</EMailAddress>
|
||||||
|
// <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
|
||||||
|
// </Request>
|
||||||
|
// </Autodiscover>
|
||||||
|
|
||||||
|
echo '<?xml version="1.0" encoding="utf-8" ?>';?>
|
||||||
|
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
|
||||||
|
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
|
||||||
|
<Account>
|
||||||
|
<AccountType>email</AccountType>
|
||||||
|
<Action>settings</Action>
|
||||||
|
<?php if ($conf["InMail"] && $conf["InMail"]["Enabled"]){
|
||||||
|
$in = $conf["InMail"];?>
|
||||||
|
<Protocol>
|
||||||
|
<Type><?php echo $in["Type"];?></Type>
|
||||||
|
<Server><?php echo $in["Server"];?></Server>
|
||||||
|
<Port><?php echo $in["Port"];?></Port>
|
||||||
|
<DomainRequired><?php echo Core::$Config["RequireAuthDomain"] ? "on" : "off";?></DomainRequired>
|
||||||
|
<LoginName><?php echo isset($matches[1]) ? $matches[1] : false ; ?></LoginName>
|
||||||
|
<SPA><?php echo $in["SPA"] ? "on" : "off";?></SPA>
|
||||||
|
<SSL><?php echo $in["SocketType"] == "SSL" ? "on" : "off";?></SSL>
|
||||||
|
<AuthRequired><?php echo $in["NoAuthRequired"] ? "off" : "on";?></AuthRequired>
|
||||||
|
</Protocol>
|
||||||
|
<?php }
|
||||||
|
if ($conf["OutMail"]&& $conf["OutMail"]["Enabled"]) {
|
||||||
|
$out = $conf["OutMail"];?>
|
||||||
|
<Protocol>
|
||||||
|
<Type><?php echo $out["Type"];?></Type>
|
||||||
|
<Server><?php echo $out["Server"];?></Server>
|
||||||
|
<Port><?php echo $out["Port"];?></Port>
|
||||||
|
<DomainRequired><?php echo Core::$Config["RequireAuthDomain"] ? "on" : "off";?></DomainRequired>
|
||||||
|
<LoginName><?php echo isset($matches[1]) ? $matches[1] : false ; ?></LoginName>
|
||||||
|
<SPA><?php echo $in["SPA"] ? "on" : "off";?></SPA>
|
||||||
|
<Encryption><?php echo $in["SocketType"];?></Encryption>
|
||||||
|
<AuthRequired><?php echo $in["NoAuthRequired"] ? "off" : "on";?></AuthRequired>
|
||||||
|
<UsePOPAuth><?php echo $in["POPAuth"] ? "on" : "off";?></UsePOPAuth>
|
||||||
|
<SMTPLast><?php echo $in["SMTPLast"] ? "on" : "off";?></SMTPLast>
|
||||||
|
</Protocol>
|
||||||
|
<?php } ?>
|
||||||
|
</Account>
|
||||||
|
</Response>
|
||||||
|
</Autodiscover>
|
5
src/public/error.php
Normal file
5
src/public/error.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
//return the json response :
|
||||||
|
$e = new Errors();
|
||||||
|
$e->throw_error("NotFound");
|
||||||
|
exit();
|
5
src/public/respond.php
Normal file
5
src/public/respond.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
//return the json response :
|
||||||
|
$e = new Responder();
|
||||||
|
$e->show_response();
|
||||||
|
exit();
|
5
src/public/unauthorized.php
Normal file
5
src/public/unauthorized.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
//return the json response :
|
||||||
|
$e = new Errors();
|
||||||
|
$e->throw_error("Unauthorized");
|
||||||
|
exit();
|
|
@ -1,60 +0,0 @@
|
||||||
<clientConfig version="1.1">
|
|
||||||
<emailProvider id="{{ index .Config.Domains 0 }}">
|
|
||||||
{{ range .Config.Domains }}<domain>{{ . }}</domain>
|
|
||||||
{{ end }}
|
|
||||||
<displayName>{{ .Email }}</displayName>
|
|
||||||
{{ with .Config.InMail }}
|
|
||||||
{{ if .Enabled }}
|
|
||||||
<incomingServer type="{{ .Type | lower }}">
|
|
||||||
<hostname>{{ .Server }}</hostname>
|
|
||||||
<port>{{ .Port }}</port>
|
|
||||||
<socketType>{{ .SocketType }}</socketType>
|
|
||||||
<username>{{ $.Email | parseUsername . }}</username>
|
|
||||||
<authentication>{{ .Authentication }}</authentication>
|
|
||||||
</incomingServer>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{ with .Config.OutMail }}
|
|
||||||
{{ if .Enabled }}
|
|
||||||
<outgoingServer type="{{ .Type | lower }}">
|
|
||||||
<hostname>{{ .Server }}</hostname>
|
|
||||||
<port>{{ .Port }}></port>
|
|
||||||
<socketType>{{ .SocketType }}</socketType>
|
|
||||||
<username>{{ $.Email | parseUsername . }}</username>
|
|
||||||
<authentication>{{ .Authentication }}</authentication>
|
|
||||||
</outgoingServer>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{ with .Config.AddressBook }}
|
|
||||||
{{ if .Enabled }}
|
|
||||||
<addressBook type="{{ .Type | lower }}">
|
|
||||||
<username>{{ $.Email | parseUsername . }}</username>
|
|
||||||
<authentication>{{ .Authentication }}</authentication>
|
|
||||||
<serverURL>{{ .Server }}addressbooks/users/{{ $.Email | parseUsername . }}/contacts/</serverURL>
|
|
||||||
</addressBook>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{ with .Config.Calendar }}
|
|
||||||
{{ if .Enabled }}
|
|
||||||
<calendar type="{{ .Type | lower }}">
|
|
||||||
<username>{{ $.Email | parseUsername . }}</username>
|
|
||||||
<authentication>{{ .Authentication }}</authentication>
|
|
||||||
<serverURL>{{ .Server }}calendars/{{ $.Email | parseUsername . }}/personal/</serverURL>
|
|
||||||
</calendar>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{ with .Config.WebMail }}
|
|
||||||
{{ if .Enabled }}
|
|
||||||
<webMail>
|
|
||||||
<loginPage url="{{ .Server }}" />
|
|
||||||
<loginPageInfo url="{{ .Server }}">
|
|
||||||
<username>{{ $.Email | parseUsername . }}</username>
|
|
||||||
<usernameField id="{{ .UsernameDivID }}" name="{{ .UsernameDivID }}" />
|
|
||||||
<passwordField name="{{ .PasswordDivName }}" />
|
|
||||||
<loginButton id="{{ .SubmitButtonID }}" name="{{ .SubmitButtonName }}"/>
|
|
||||||
</loginPageInfo>
|
|
||||||
</webMail>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
</emailProvider>
|
|
||||||
</clientConfig>
|
|
|
@ -1,39 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
|
|
||||||
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
|
|
||||||
<Account>
|
|
||||||
<AccountType>email</AccountType>
|
|
||||||
<Action>settings</Action>
|
|
||||||
{{ with .Config.InMail }}
|
|
||||||
{{ if .Enabled }}
|
|
||||||
<Protocol>
|
|
||||||
<Type>{{ .Type }}</Type>
|
|
||||||
<Server>{{ .Server }}</Server>
|
|
||||||
<Port>{{ .Port }}</Port>
|
|
||||||
<DomainRequired>{{ .UsernameIsFQDN | onoff }}</DomainRequired>
|
|
||||||
<LoginName>{{ $.Email | parseUsername . }}</LoginName>
|
|
||||||
<SPA>{{ .SPA | onoff }}</SPA>
|
|
||||||
<SSL>{{ if eq .SocketType "SSL" }}on{{ else }}off{{ end }}</SSL>
|
|
||||||
<AuthRequired>{{ not .NoAuthRequired | onoff }}</AuthRequired>
|
|
||||||
</Protocol>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{ with .Config.OutMail }}
|
|
||||||
{{ if .Enabled }}
|
|
||||||
<Protocol>
|
|
||||||
<Type>{{ .Type }}</Type>
|
|
||||||
<Server>{{ .Server }}</Server>
|
|
||||||
<Port>{{ .Port }}</Port>
|
|
||||||
<DomainRequired>{{ .UsernameIsFQDN | onoff }}</DomainRequired>
|
|
||||||
<LoginName>{{ $.Email | parseUsername . }}</LoginName>
|
|
||||||
<SPA>{{ .SPA | onoff }}</SPA>
|
|
||||||
<Encryption>{{ .SocketType }}</Encryption>
|
|
||||||
<AuthRequired>{{ not .NoAuthRequired | onoff }}</AuthRequired>
|
|
||||||
<UsePOPAuth>{{ .POPAuth | onoff }}</UsePOPAuth>
|
|
||||||
<SMTPLast>{{ .SMTPLast | onoff }}</SMTPLast>
|
|
||||||
</Protocol>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
</Account>
|
|
||||||
</Response>
|
|
||||||
</Autodiscover>
|
|
|
@ -1,44 +0,0 @@
|
||||||
package handler
|
|
||||||
import (
|
|
||||||
|
|
||||||
. "mailautoconf/global"
|
|
||||||
. "mailautoconf/global/structs"
|
|
||||||
"mailautoconf/global/logger"
|
|
||||||
"mailautoconf/web/responses"
|
|
||||||
"strings"
|
|
||||||
"net/http"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
func WebHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ThisSession = Session{}
|
|
||||||
ThisSession.ResponseWriter = w
|
|
||||||
ThisSession.Request = r
|
|
||||||
|
|
||||||
ThisSession.ID = NewSessionID()
|
|
||||||
url := fmt.Sprintf("%s", r.URL)
|
|
||||||
logger.Log("Session ", ThisSession.ID, " Request For : ", url )
|
|
||||||
ThisSession.IP = GetSessionIP()
|
|
||||||
|
|
||||||
ThisSession.Path = strings.ToLower(r.URL.Path[1:])
|
|
||||||
if ThisSession.Path == "" {
|
|
||||||
ThisSession.Path = "none"
|
|
||||||
}
|
|
||||||
switch ThisSession.Path {
|
|
||||||
case "mail/config-v1.1.xml",
|
|
||||||
"mail/autoconfig.xml":
|
|
||||||
ThisSession.WebContent = responses.MozAutoconfig()
|
|
||||||
case "autodiscover/autodiscover.xml":
|
|
||||||
ThisSession.WebContent = responses.MsAutoDiscoverXML()
|
|
||||||
case "autodiscover/autodiscover.json":
|
|
||||||
ThisSession.WebContent = responses.MsAutoDiscoverJSON()
|
|
||||||
case "get/config":
|
|
||||||
ThisSession.WebContent = responses.OurConfig()
|
|
||||||
default:
|
|
||||||
ThisSession.WebContent = responses.DefaultResponse()
|
|
||||||
}
|
|
||||||
webOutput()
|
|
||||||
}
|
|
||||||
func webOutput(){
|
|
||||||
ThisSession.ResponseWriter.Header().Set("Content-Type", ThisSession.ContentType)
|
|
||||||
fmt.Fprintf(ThisSession.ResponseWriter, ThisSession.WebContent)
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
package responses
|
|
||||||
import (
|
|
||||||
"mailautoconf/global"
|
|
||||||
"mailautoconf/global/logger"
|
|
||||||
. "mailautoconf/global/structs"
|
|
||||||
// "text/template"
|
|
||||||
"fmt"
|
|
||||||
// "path"
|
|
||||||
"strings"
|
|
||||||
"bytes"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
var email string
|
|
||||||
|
|
||||||
func MozAutoconfig() string {
|
|
||||||
// The below link has config-v1.1.xml information
|
|
||||||
// https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
|
|
||||||
|
|
||||||
// parse the querystring
|
|
||||||
logger.CheckError(global.ThisSession.Request.ParseForm())
|
|
||||||
|
|
||||||
// build the response
|
|
||||||
response := Response{}
|
|
||||||
response.Email = global.ThisSession.Request.FormValue("emailaddress")
|
|
||||||
email = response.Email
|
|
||||||
response.Config = global.MainConfig
|
|
||||||
|
|
||||||
// set content type to XML
|
|
||||||
global.ThisSession.ContentType = "application/xml"
|
|
||||||
|
|
||||||
// execute the template
|
|
||||||
var result bytes.Buffer
|
|
||||||
template := global.Templates["autoconfig.xml"]
|
|
||||||
err := template.Execute(&result, response)
|
|
||||||
logger.CheckError(err)
|
|
||||||
|
|
||||||
// return our string of xml
|
|
||||||
return result.String()
|
|
||||||
}
|
|
||||||
func MsAutoDiscoverXML() string {
|
|
||||||
// MS Outlook Autodiscover.xml
|
|
||||||
//
|
|
||||||
// Example POST Request (sent from client) :
|
|
||||||
// <?xml version="1.0" \?\>
|
|
||||||
// <Autodiscover xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
|
|
||||||
// <Request>
|
|
||||||
// <EMailAddress>your@email.address</EMailAddress>
|
|
||||||
// <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
|
|
||||||
// </Request>
|
|
||||||
// </Autodiscover>
|
|
||||||
|
|
||||||
// Parse the form to get the values
|
|
||||||
logger.CheckError(global.ThisSession.Request.ParseForm())
|
|
||||||
|
|
||||||
// convert the input to a string so we can extract the email address
|
|
||||||
form := fmt.Sprintf("%s",global.ThisSession.Request.Form)
|
|
||||||
|
|
||||||
// fine the EMailAddress section
|
|
||||||
find := regexp.MustCompile(`\<EMailAddress\>(.*?)\<\/EMailAddress\>`)
|
|
||||||
email = find.FindString(form)
|
|
||||||
|
|
||||||
// replace the tags
|
|
||||||
replace := regexp.MustCompile(`\<[\/]?EMailAddress\>`)
|
|
||||||
email = replace.ReplaceAllString(email,``)
|
|
||||||
|
|
||||||
logger.Log("Session ",global.ThisSession.ID ," Request for email : ",email)
|
|
||||||
// build the reponse
|
|
||||||
response := Response{}
|
|
||||||
response.Email = email
|
|
||||||
response.Config = global.MainConfig
|
|
||||||
|
|
||||||
// execute the template
|
|
||||||
template := global.Templates["autodiscover.xml"]
|
|
||||||
global.ThisSession.ContentType = "application/xml"
|
|
||||||
var result bytes.Buffer
|
|
||||||
err := template.Execute(&result, response)
|
|
||||||
logger.CheckError(err)
|
|
||||||
|
|
||||||
// return our string of xml
|
|
||||||
return result.String()
|
|
||||||
}
|
|
||||||
func MsAutoDiscoverJSON() string {
|
|
||||||
// MS Outlook Autodiscover.json - undocumented
|
|
||||||
//
|
|
||||||
// Example Request
|
|
||||||
// /autodiscover/autodiscover.json?Email=you@your.domain&Protocol=Autodiscoverv1&RedirectCount=1
|
|
||||||
email = global.ThisSession.Request.FormValue("Email")
|
|
||||||
protocol := global.ThisSession.Request.FormValue("Protocol")
|
|
||||||
global.ThisSession.ContentType = "application/json"
|
|
||||||
switch strings.ToLower(protocol) {
|
|
||||||
case "autodiscoverv1":
|
|
||||||
response := MSAutodiscoverJSONResponse{}
|
|
||||||
response.Protocol = "AutodiscoverV1"
|
|
||||||
response.Url = fmt.Sprintf("%s/Autodiscover/Autodiscover.xml", global.MainConfig.BaseURL)
|
|
||||||
return global.JSONify(response)
|
|
||||||
default:
|
|
||||||
response := MSAutodiscoverJSONError{}
|
|
||||||
response.ErrorCode = "InvalidProtocol";
|
|
||||||
response.ErrorMessage = fmt.Sprintf("The given protocol value '%s' is invalid. Supported values are 'AutodiscoverV1'", protocol)
|
|
||||||
return global.JSONify(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func DefaultResponse() string {
|
|
||||||
response := Response{}
|
|
||||||
response.Url = global.ThisSession.Path
|
|
||||||
global.ThisSession.ContentType = "application/json"
|
|
||||||
response.ContentType = global.ThisSession.ContentType
|
|
||||||
response.Message = "Success! Things are working! Please request a valid URL i.e. /mail/config-v1.1.xml";
|
|
||||||
return global.JSONify(response)
|
|
||||||
}
|
|
||||||
func OurConfig() string {
|
|
||||||
global.ThisSession.ContentType = "application/json"
|
|
||||||
content := global.JSONifyConfig(global.MainConfig)
|
|
||||||
return content
|
|
||||||
}
|
|
4
test-entry.sh
Executable file
4
test-entry.sh
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
a2enmod rewrite
|
||||||
|
service apache2 stop
|
||||||
|
exec bash /entrypoint.sh
|
11
test-server.sh
Normal file
11
test-server.sh
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
podman run --name mailautoconf-test \
|
||||||
|
--rm \
|
||||||
|
-p "8010:80" \
|
||||||
|
-v ./src:/var/www/html/ \
|
||||||
|
-v ./config:/var/www/html/config \
|
||||||
|
-v ./test-entry.sh:/test-entry.sh \
|
||||||
|
-v ./entrypoint.sh:/entrypoint.sh \
|
||||||
|
--entrypoint "/bin/bash" \
|
||||||
|
php:7.4-apache \
|
||||||
|
/test-entry.sh
|
Loading…
Reference in a new issue