|
| 1 | +#!/usr/bin/php |
| 2 | +<?php |
| 3 | +# |
| 4 | +# ispc-import-csv-email.php: import email accounts from csv into ispconfig |
| 5 | +# |
| 6 | + |
| 7 | +# ISPConfig remote api params |
| 8 | +$remote_user = 'importer'; |
| 9 | +$remote_pass = 'apipassword'; |
| 10 | +$remote_url = 'https://yourserver.com:8080/remote/json.php'; |
| 11 | + |
| 12 | +# CSV file |
| 13 | +$csv_file="/home/migrations/test.csv"; |
| 14 | + |
| 15 | + |
| 16 | +# csv file format (first line is header names, column order does not matter): |
| 17 | +# |
| 18 | +# "email","password","quota","name","cc","bcc","move_junk","autoresponder","autoresponder_text","virus_lover","spam_lover" |
| 19 | +# "api_standard@apitest.com","insecure","150","API User Insert: Standard Mailbox","","","yes","no","this is vacation text, although vacation is not enabled","N","N" |
| 20 | +# "api_no_spambox@apitest.com","insecure","150","API User Insert: Mailbox with move_junk off","","","no","no","this is vacation text, although vacation is not enabled","N","N" |
| 21 | +# "api_vacation@apitest.com","insecure","150","API User Insert: Mailbox with vacation","","","yes","yes","this is vacation text, with vacation enabled","N","N" |
| 22 | +# "api_forward@apitest.com","insecure","150","API User Insert: Mail Forward","your-test-addr@test.com","","no","no","this is vacation text, although vacation is not enabled","N","N" |
| 23 | +# "api_both1@apitest.com","insecure","150","API User Insert: Mailbox with forward via cc","your-test-addr@test.com","","yes","no","this is vacation text, although vacation is not enabled","N","N" |
| 24 | +# "api_both2@apitest.com","insecure","150","API User Insert: Mailbox with forward via bcc","","your-test-addr@test.com","yes","no","this is vacation text, although vacation is not enabled","N","N" |
| 25 | +# "api_virus_lover@apitest.com","insecure","150","API User Insert: Mailbox with virus_lover","","","yes","no","","Y","N" |
| 26 | +# "api_spam_lover@apitest.com","insecure","150","API User Insert: Mailbox with spam_lover","","","yes","no","","N","Y" |
| 27 | +# "api_both_lover@apitest.com","insecure","150","API User Insert: Mailbox with virus_lover and spam_lover","","","yes","no","","Y","Y" |
| 28 | + |
| 29 | + |
| 30 | +/** |
| 31 | + * Call REST endpoint. |
| 32 | + */ |
| 33 | +function restCall( $method, $data ) { |
| 34 | + global $remote_url; |
| 35 | + |
| 36 | + if(!is_array($data)) return false; |
| 37 | + $json = json_encode($data); |
| 38 | + |
| 39 | + $curl = curl_init(); |
| 40 | + curl_setopt($curl, CURLOPT_POST, 1); |
| 41 | + |
| 42 | + if($data) curl_setopt($curl, CURLOPT_POSTFIELDS, $json); |
| 43 | + |
| 44 | + // needed for self-signed cert |
| 45 | + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); |
| 46 | + //curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); |
| 47 | + // end of needed for self-signed cert |
| 48 | + |
| 49 | + curl_setopt($curl, CURLOPT_URL, $remote_url . '?' . $method); |
| 50 | + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); |
| 51 | + |
| 52 | + $result = curl_exec($curl); |
| 53 | + curl_close($curl); |
| 54 | + |
| 55 | + return $result; |
| 56 | +} |
| 57 | + |
| 58 | +$session_id = ''; |
| 59 | + |
| 60 | +/** |
| 61 | + * Logout of active session and die with message. |
| 62 | + */ |
| 63 | +function session_die( $msg ) { |
| 64 | + global $session_id; |
| 65 | + |
| 66 | + if ( isset( $session_id ) && $session_id ) { |
| 67 | + $result = restCall( 'logout', [ 'session_id' => $session_id ] ); |
| 68 | + $result || die( "$msg\nAdditionally, could not get logout result, session id $session_id may now be abandoned.\n" ); |
| 69 | + } |
| 70 | + |
| 71 | + die( "$msg\n" ); |
| 72 | +} |
| 73 | + |
| 74 | +/** |
| 75 | + * Make api call, checking for errors and return 'response' from the decoded data. Opens session if required. |
| 76 | + */ |
| 77 | +function apiCall( ...$args ) { |
| 78 | + global $remote_user, $remote_pass, $session_id; |
| 79 | + |
| 80 | + // login to remote api and obtain session id if needed |
| 81 | + if ( ! ( isset( $session_id ) && $session_id ) ) { |
| 82 | + $result = restCall( 'login', [ 'username' => $remote_user, 'password' => $remote_pass, 'client_login' => false, ] ); |
| 83 | + |
| 84 | + if ( $result ) { |
| 85 | + $result = json_decode( $result, true ); |
| 86 | + if ( ! $result ) { |
| 87 | + die( "Error: unable to login to remote api (json_decode failed)\n" ); |
| 88 | + } |
| 89 | + |
| 90 | + if ( isset( $result['response'] ) ) { |
| 91 | + $session_id = $result['response']; |
| 92 | + } else { |
| 93 | + die( "Error: failed to obtain session id from remote api login\n" ); |
| 94 | + } |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + $rest_args = func_get_args(); |
| 99 | + $method = array_shift( $rest_args ); |
| 100 | + |
| 101 | + $result = restCall( $method, array_merge( [ 'session_id' => $session_id, ], ...$rest_args ) ); |
| 102 | + |
| 103 | + if ( $result ) $data = json_decode( $result, true ); |
| 104 | + else session_die( "Could not get $method result" ); |
| 105 | + |
| 106 | + if ( isset( $data['code'] ) && 'ok' != $data['code'] ) { |
| 107 | + $msg = "$method returned " . $data['code'] |
| 108 | + . ( isset( $data['message'] ) ? ": " . $data['message'] . "\n" : "\n" ); |
| 109 | + session_die( $msg ); |
| 110 | + } |
| 111 | + |
| 112 | + return ( isset( $data['response'] ) ? $data['response'] : $data ); |
| 113 | +} |
| 114 | + |
| 115 | +if ( ! file_exists( "$csv_file" ) ) { |
| 116 | + die( "CSV file ($csv_file) not found.\n" ); |
| 117 | +} |
| 118 | + |
| 119 | +// get all mail policies |
| 120 | +$mail_policies = apiCall( 'mail_policy_get', [ 'primary_id' => [] ] ); |
| 121 | +if ( ! $mail_policies ) { |
| 122 | + session_die( "Error: could not look up mail policies\n" ); |
| 123 | +} |
| 124 | + |
| 125 | +// get all spamfilter_user settings |
| 126 | +$mail_spamfilter_users = apiCall( 'mail_spamfilter_user_get', [ 'primary_id' => [] ] ); |
| 127 | +if ( ! $mail_spamfilter_users ) { |
| 128 | + session_die( "Error: could not look up mail spamfilter users\n" ); |
| 129 | +} |
| 130 | + |
| 131 | +$mail_domains = []; |
| 132 | + |
| 133 | +// Read csv file, map rows and loop through them |
| 134 | +$rows = array_map( 'str_getcsv', file( $csv_file ) ); |
| 135 | +$header = array_shift( $rows ); |
| 136 | +$email_idx = array_search( 'email', $header ); |
| 137 | +if ( $email_idx === FALSE ) { |
| 138 | + session_die( "Error in csv file: 'email' field not found.\n" ); |
| 139 | +} |
| 140 | +$csv = []; |
| 141 | +foreach( $rows as $row ) { |
| 142 | + $email = $row[$email_idx]; |
| 143 | + $domain = substr( $email, strpos( $email, '@' ) + 1 ); |
| 144 | + |
| 145 | + if ( is_array( $row ) && count( $header ) == count( $row ) ) { |
| 146 | + $csv[$email] = array_combine( $header, $row ); |
| 147 | + } else { |
| 148 | + print "Error in csv file: problem parsing email '$email'\n"; |
| 149 | + continue; |
| 150 | + } |
| 151 | + |
| 152 | + // look up mail_domain record for this domain |
| 153 | + if ( ! isset( $mail_domains[$domain] ) ) { |
| 154 | + $data = apiCall( 'mail_domain_get_by_domain', [ 'domain' => $domain ] ); |
| 155 | + |
| 156 | + if ( is_array( $data ) && isset( $data[0] ) ) { |
| 157 | + |
| 158 | + // unset these (large and don't need them) |
| 159 | + unset( $data[0]['dkim'] ); |
| 160 | + unset( $data[0]['dkim_selector'] ); |
| 161 | + unset( $data[0]['dkim_public'] ); |
| 162 | + unset( $data[0]['dkim_private'] ); |
| 163 | + |
| 164 | + $mail_domains[$domain] = $data[0]; |
| 165 | + |
| 166 | + foreach ( $mail_spamfilter_users as $msu ) { |
| 167 | + if ( $msu['email'] == "@$domain" && $msu['server_id'] == $mail_domains[$domain]['server_id'] ) { |
| 168 | + $mail_domains[$domain]['spamfilter_policy_id'] = $msu['policy_id']; |
| 169 | + } |
| 170 | + } |
| 171 | + } else { |
| 172 | + $mail_domains[$domain] = [ 'domain_id' => -1, 'domain' => $domain, ]; |
| 173 | + print( "Error: mail_domain $domain does not exist, you must create it first.\n" ); |
| 174 | + } |
| 175 | + } |
| 176 | +} |
| 177 | + |
| 178 | +// dump manually created account to compare values |
| 179 | +//$data = apiCall( 'mail_user_get', [ 'primary_id' => [ 'email' => 'manual@apitest.com' ] ] ); |
| 180 | +//var_dump( $data, true ); |
| 181 | + |
| 182 | +foreach ( $csv as $record ) { |
| 183 | + $email = $record['email']; |
| 184 | + $addr = substr( $email, 0, strpos( $email, '@' ) ); |
| 185 | + $domain = substr( $email, strpos( $email, '@' ) + 1 ); |
| 186 | + |
| 187 | + // ensure we have mail_domain info |
| 188 | + if ( ! isset( $mail_domains[$domain] ) || -1 == $mail_domains[$domain]['domain_id'] ) { |
| 189 | + print "Config for domain $domain not available, cannot add email $email.\n"; |
| 190 | + continue; |
| 191 | + } |
| 192 | + |
| 193 | + // skip if email already exists |
| 194 | + $data = apiCall( 'mail_user_get', [ 'primary_id' => [ 'email' => $email ] ] ); |
| 195 | + if ( is_array( $data ) && isset( $data[0] ) && isset( $data[0]['mailuser_id'] ) ) { |
| 196 | + print "Email $email already exists, skipping.\n"; |
| 197 | + continue; |
| 198 | + } |
| 199 | + |
| 200 | + // get client_id for this sys_userid |
| 201 | + if ( isset( $mail_domains[$domain]['client_id'] ) ) { |
| 202 | + $client_id = $mail_domains[$domain]['client_id']; |
| 203 | + } else { |
| 204 | + $client_id = apiCall( 'client_get_id', [ 'sys_userid' => $mail_domains[$domain]['sys_userid'] ] ); |
| 205 | + if ( ! $client_id ) { |
| 206 | + print "Error: unable to determine client_id for $domain (sys_userid is " . $mail_domains[$domain]['sys_userid'] . "),\n"; |
| 207 | + print "cannot create mailbox for Email $email\n"; |
| 208 | + continue; |
| 209 | + } |
| 210 | + $mail_domains[$domain]['client_id'] = $client_id; |
| 211 | + } |
| 212 | + |
| 213 | + // mail_user_add parameters for this email |
| 214 | + $params = [ 'params' => [ |
| 215 | + 'server_id' => $mail_domains[$domain]['server_id'], |
| 216 | + 'email' => $email, |
| 217 | + 'login' => $email, |
| 218 | + 'password' => $record['password'], |
| 219 | + 'name' => $record['name'], |
| 220 | + 'uid' => 5000, |
| 221 | + 'gid' => 5000, |
| 222 | + 'maildir' => "/var/vmail/$domain/$addr", |
| 223 | + 'quota' => $record['quota'] * 1024 * 1024, |
| 224 | + 'cc' => implode( ',', array_filter( [ $record['cc'], $record['bcc'] ] ) ), |
| 225 | + 'homedir' => "/var/vmail/", |
| 226 | + 'autoresponder' => ( preg_match( '/^y/i', $record['autoresponder'] ) ? 'y' : 'n' ), |
| 227 | + 'autoresponder_start_date' => date( 'Y-m-d H:i:s' ), |
| 228 | + 'autoresponder_end_date' => date( '2024-m-d H:i:s' ), |
| 229 | + 'autoresponder_text' => $record['autoresponder_text'], |
| 230 | + 'move_junk' => ( preg_match( '/^y/i', $record['move_junk'] ) ? 'y' : 'n' ), |
| 231 | + 'custom_mailfilter' => "", |
| 232 | + 'postfix' => 'y', |
| 233 | + 'access' => 'y', |
| 234 | + // 'disableimap' => 'n', |
| 235 | + // 'disablepop3' => 'n', |
| 236 | + // 'disabledeliver' => 'n', |
| 237 | + // 'disablesmtp' => 'n', |
| 238 | + ], |
| 239 | + ]; |
| 240 | + |
| 241 | + // add mail user |
| 242 | + $data = apiCall( 'mail_user_add', [ 'client_id' => $client_id ], $params ); |
| 243 | + |
| 244 | + if ( ! $data ) { |
| 245 | + print "mail_user_add may have a problem inserting $email\n"; |
| 246 | + continue; |
| 247 | + } |
| 248 | + |
| 249 | + //$data = apiCall( 'mail_user_get', [ 'primary_id' => [ 'email' => $email ] ] ); |
| 250 | + //var_dump( $data, true ); |
| 251 | + |
| 252 | + // determine mail policy |
| 253 | + $spam_lover = ( preg_match( '/^y/i', $record['move_junk'] ) ? $record['spam_lover'] : 'N' ); |
| 254 | + $virus_lover = $record['virus_lover']; |
| 255 | + $spamfilter_policy_id = null; |
| 256 | + |
| 257 | + // check domain's policy settings for bypass_spam_checks == 'N' and matching spam_lover/virus_lover, |
| 258 | + // if a match, we're done |
| 259 | + if ( isset( $mail_domains[$domain]['spamfilter_policy_id'] ) ) { |
| 260 | + foreach ( $mail_policies as $policy ) { |
| 261 | + if ( $policy['id'] == $mail_domains[$domain]['spamfilter_policy_id'] ) { |
| 262 | + if ( 'N' == $policy['bypass_spam_checks'] && $policy['spam_lover'] == $spam_lover && $policy['virus_lover'] == $virus_lover ) { |
| 263 | + $spamfilter_policy_id = $policy['id']; |
| 264 | + } |
| 265 | + } |
| 266 | + } |
| 267 | + } |
| 268 | + // if domain's policy doesn't match, loop through all policies to find a match and insert it |
| 269 | + if ( null === $spamfilter_policy_id ) { |
| 270 | + foreach ( $mail_policies as $policy ) { |
| 271 | + if ( 'Y' == $policy['bypass_spam_checks'] ) { |
| 272 | + continue; |
| 273 | + } |
| 274 | + if ( $policy['spam_lover'] == $spam_lover && $policy['virus_lover'] == $virus_lover ) { |
| 275 | + $spamfilter_policy_id = $policy['id']; |
| 276 | + |
| 277 | + // mail_spamfilter_user entry for this user / policy_id |
| 278 | + $params = [ 'params' => [ |
| 279 | + 'server_id' => $mail_domains[$domain]['server_id'], |
| 280 | + 'priority' => "10", |
| 281 | + 'policy_id' => $policy['id'], |
| 282 | + 'email' => $email, |
| 283 | + 'fullname' => $email, |
| 284 | + 'local' => "Y", |
| 285 | + ], |
| 286 | + ]; |
| 287 | + |
| 288 | + $data = apiCall( 'mail_spamfilter_user_add', [ 'client_id' => $client_id ], $params ); |
| 289 | + |
| 290 | + // either we inserted a spamfilter_user or it failed, |
| 291 | + // either way, on to the next email |
| 292 | + continue 2; |
| 293 | + } |
| 294 | + } |
| 295 | + } |
| 296 | +} |
| 297 | + |
| 298 | + |
| 299 | +// logout so session id is cleaned up |
| 300 | +if ( isset( $session_id ) && $session_id ) { |
| 301 | + $result = restCall( 'logout', [ 'session_id' => $session_id ] ); |
| 302 | + $result || die( "Could not get logout result, session id $session_id may now be abandoned.\n" ); |
| 303 | +} |
| 304 | + |
| 305 | +exit(); |
0 commit comments