You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. #!/usr/bin/php
  2. <?php
  3. /**
  4. * Command-line code generation utility to automate administrator tasks.
  5. *
  6. * Shell dispatcher class
  7. *
  8. * PHP versions 4 and 5
  9. *
  10. * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
  11. * Copyright 2005-2008, Cake Software Foundation, Inc.
  12. * 1785 E. Sahara Avenue, Suite 490-204
  13. * Las Vegas, Nevada 89104
  14. * Modified for PostfixAdmin by Valkum 2011
  15. * Modified for PostfixAdmin by Christian Boltz 2011-2013
  16. *
  17. * Copyright 2010
  18. *
  19. * Licensed under The MIT License
  20. * Redistributions of files must retain the above copyright notice.
  21. *
  22. * @filesource
  23. * @copyright Copyright 2005-2008, Cake Software Foundation, Inc.
  24. * @link http://postfixadmin.sourceforge.net/ Postfixadmin on Sourceforge
  25. * @package postfixadmin
  26. * @subpackage -
  27. * @since -
  28. * @version $Revision$
  29. * @modifiedby $LastChangedBy$
  30. * @lastmodified $Date$
  31. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  32. */
  33. class PostfixAdmin {
  34. /**
  35. * Version
  36. *
  37. * @var string
  38. */
  39. public $version ='0.2';
  40. /**
  41. * Standard input stream.
  42. *
  43. * @var filehandle
  44. */
  45. public $stdin;
  46. /**
  47. * Standard output stream.
  48. *
  49. * @var filehandle
  50. */
  51. public $stdout;
  52. /**
  53. * Standard error stream.
  54. *
  55. * @var filehandle
  56. */
  57. public $stderr;
  58. /**
  59. * Contains command switches parsed from the command line.
  60. *
  61. * @var array
  62. */
  63. public $params = array();
  64. /**
  65. * Contains arguments parsed from the command line.
  66. *
  67. * @var array
  68. */
  69. public $args = array();
  70. /**
  71. * The file name of the shell that was invoked.
  72. *
  73. * @var string
  74. */
  75. public $shell = null;
  76. /**
  77. * The class name of the shell that was invoked.
  78. *
  79. * @var string
  80. */
  81. public $shellClass = null;
  82. /**
  83. * The command called if public methods are available.
  84. *
  85. * @var string
  86. */
  87. public $shellCommand = null;
  88. /**
  89. * The name of the shell in camelized.
  90. *
  91. * @var string
  92. */
  93. public $shellName = null;
  94. /**
  95. * Constructor
  96. *
  97. * @param array $args the argv.
  98. */
  99. public function __construct($args = array()) {
  100. set_time_limit(0);
  101. $this->__initConstants();
  102. $this->parseParams($args);
  103. $this->__initEnvironment();
  104. }
  105. /**
  106. * Defines core configuration.
  107. */
  108. private function __initConstants() {
  109. ini_set('display_errors', '1');
  110. ini_set('error_reporting', E_ALL);
  111. ini_set('html_errors', false);
  112. ini_set('implicit_flush', true);
  113. ini_set('max_execution_time', 0);
  114. define('DS', DIRECTORY_SEPARATOR);
  115. define('CORE_INCLUDE_PATH', dirname(__FILE__));
  116. define('CORE_PATH', dirname(CORE_INCLUDE_PATH) ); # CORE_INCLUDE_PATH/../
  117. if(!defined('POSTFIXADMIN')) { # already defined if called from setup.php
  118. define('POSTFIXADMIN', 1); # checked in included files
  119. }
  120. }
  121. /**
  122. * Defines current working environment.
  123. */
  124. private function __initEnvironment() {
  125. $this->stdin = fopen('php://stdin', 'r');
  126. $this->stdout = fopen('php://stdout', 'w');
  127. $this->stderr = fopen('php://stderr', 'w');
  128. if (!$this->__bootstrap()) {
  129. $this->stderr("");
  130. $this->stderr("Unable to load.");
  131. $this->stderr("\tMake sure /config.inc.php exists in " . PATH);
  132. exit(1);
  133. }
  134. if (basename(__FILE__) != basename($this->args[0])) {
  135. $this->stderr('Warning: the dispatcher may have been loaded incorrectly, which could lead to unexpected results...');
  136. if ($this->getInput('Continue anyway?', array('y', 'n'), 'y') == 'n') {
  137. exit(1);
  138. }
  139. }
  140. $this->shiftArgs();
  141. }
  142. /**
  143. * Initializes the environment and loads the Cake core.
  144. *
  145. * @return boolean Success.
  146. */
  147. private function __bootstrap() {
  148. if ($this->params['webroot'] != '' ) {
  149. define('PATH', $this->params['webroot'] );
  150. } else {
  151. define('PATH', CORE_PATH);
  152. }
  153. if (!file_exists(PATH)) {
  154. $this->stderr( PATH . " don't exists");
  155. return false;
  156. }
  157. # make sure global variables fron functions.inc.php end up in the global namespace, instead of being local to this function
  158. global $version, $min_db_version;
  159. if (!require_once(PATH . '/common.php')) {
  160. $this->stderr("Failed to load " . PATH . '/common.php');
  161. return false;
  162. }
  163. return true;
  164. }
  165. /**
  166. * Dispatches a CLI request
  167. */
  168. public function dispatch() {
  169. $CONF = Config::read('all');
  170. check_db_version(); # ensure the database layout is up to date
  171. if (!isset($this->args[0])) {
  172. $this->help();
  173. return;
  174. }
  175. $this->shell = $this->args[0];
  176. $this->shiftArgs();
  177. $this->shellName = ucfirst($this->shell);
  178. $this->shellClass = $this->shellName . 'Handler';
  179. if ($this->shell == 'help') {
  180. $this->help();
  181. return;
  182. }
  183. # TODO: move shells/shell.php to model/ to enable autoloading
  184. if (!class_exists('Shell')) {
  185. require CORE_INCLUDE_PATH . DS . "shells" . DS . 'shell.php';
  186. }
  187. $command = 'help'; # not the worst default ;-)
  188. if (isset($this->args[0])) {
  189. $command = $this->args[0];
  190. }
  191. $this->shellCommand = $command;
  192. $this->shellClass = 'Cli' . ucfirst($command);
  193. if (ucfirst($command) == 'Add' || ucfirst($command) == 'Update') {
  194. $this->shellClass = 'CliEdit';
  195. }
  196. if (!class_exists($this->shellClass)) {
  197. $this->stderr('Unknown task ' . $this->shellCommand);
  198. return;
  199. }
  200. $shell = new $this->shellClass($this);
  201. $shell->handler_to_use = ucfirst($this->shell) . 'Handler';
  202. if (!class_exists($shell->handler_to_use)) {
  203. $this->stderr('Unknown module ' . $this->shell);
  204. return;
  205. }
  206. $task = ucfirst($command);
  207. $shell->new = 0;
  208. if ($task == 'Add') {
  209. $shell->new = 1;
  210. }
  211. # TODO: add a way to Cli* to signal if the selected handler is supported (for example, not all *Handler support changing the password)
  212. if (strtolower(get_parent_class($shell)) == 'shell') {
  213. $shell->initialize();
  214. $handler = new $shell->handler_to_use;
  215. if (in_array($task, $handler->taskNames)) {
  216. $this->shiftArgs();
  217. $shell->startup();
  218. if (isset($this->args[0]) && $this->args[0] == 'help') {
  219. if (method_exists($shell, 'help')) {
  220. $shell->help();
  221. exit();
  222. } else {
  223. $this->help();
  224. }
  225. }
  226. $shell->execute();
  227. return;
  228. }
  229. }
  230. $classMethods = get_class_methods($shell);
  231. $privateMethod = $missingCommand = false;
  232. if ((in_array($command, $classMethods) || in_array(strtolower($command), $classMethods)) && strpos($command, '_', 0) === 0) {
  233. $privateMethod = true;
  234. }
  235. if (!in_array($command, $classMethods) && !in_array(strtolower($command), $classMethods)) {
  236. $missingCommand = true;
  237. }
  238. $protectedCommands = array(
  239. 'initialize','in','out','err','hr',
  240. 'createfile', 'isdir','copydir','object','tostring',
  241. 'requestaction','log','cakeerror', 'shelldispatcher',
  242. '__initconstants','__initenvironment','__construct',
  243. 'dispatch','__bootstrap','getinput','stdout','stderr','parseparams','shiftargs'
  244. );
  245. if (in_array(strtolower($command), $protectedCommands)) {
  246. $missingCommand = true;
  247. }
  248. if ($missingCommand && method_exists($shell, 'main')) {
  249. $shell->startup();
  250. $shell->main();
  251. } elseif (!$privateMethod && method_exists($shell, $command)) {
  252. $this->shiftArgs();
  253. $shell->startup();
  254. $shell->{$command}();
  255. } else {
  256. $this->stderr("Unknown {$this->shellName} command '$command'.\nFor usage, try 'postfixadmin-cli {$this->shell} help'.\n\n");
  257. }
  258. }
  259. /**
  260. * Prompts the user for input, and returns it.
  261. *
  262. * @param string $prompt Prompt text.
  263. * @param mixed $options Array or string of options.
  264. * @param string $default Default input value.
  265. * @return Either the default value, or the user-provided input.
  266. */
  267. public function getInput($prompt, $options = null, $default = null) {
  268. if (!is_array($options)) {
  269. $print_options = '';
  270. } else {
  271. $print_options = '(' . implode('/', $options) . ')';
  272. }
  273. if ($default == null) {
  274. $this->stdout($prompt . " $print_options \n" . '> ', false);
  275. } else {
  276. $this->stdout($prompt . " $print_options \n" . "[$default] > ", false);
  277. }
  278. $result = fgets($this->stdin);
  279. if ($result === false){
  280. exit(1);
  281. }
  282. $result = trim($result);
  283. if ($default != null && empty($result)) {
  284. return $default;
  285. }
  286. return $result;
  287. }
  288. /**
  289. * Outputs to the stdout filehandle.
  290. *
  291. * @param string $string String to output.
  292. * @param boolean $newline If true, the outputs gets an added newline.
  293. */
  294. public function stdout($string, $newline = true) {
  295. if ($newline) {
  296. fwrite($this->stdout, $string . "\n");
  297. } else {
  298. fwrite($this->stdout, $string);
  299. }
  300. }
  301. /**
  302. * Outputs to the stderr filehandle.
  303. *
  304. * @param string $string Error text to output.
  305. */
  306. public function stderr($string) {
  307. fwrite($this->stderr, 'Error: '. $string . "\n");
  308. }
  309. /**
  310. * Parses command line options
  311. *
  312. * @param array $params Parameters to parse
  313. */
  314. public function parseParams($params) {
  315. $this->__parseParams($params);
  316. $defaults = array('webroot' => CORE_PATH);
  317. $params = array_merge($defaults, array_intersect_key($this->params, $defaults));
  318. $isWin = array_filter(array_map('strpos', $params, array('\\')));
  319. $params = str_replace('\\', '/', $params);
  320. if (!empty($matches[0]) || !empty($isWin)) {
  321. $params = str_replace('/', '\\', $params);
  322. }
  323. $this->params = array_merge($this->params, $params);
  324. }
  325. /**
  326. * Helper for recursively paraing params
  327. *
  328. * @return array params
  329. */
  330. private function __parseParams($params) {
  331. $count = count($params);
  332. for ($i = 0; $i < $count; $i++) {
  333. if (isset($params[$i])) {
  334. if ($params[$i] != '' && $params[$i]{0} === '-') {
  335. $key = substr($params[$i], 1);
  336. $this->params[$key] = true;
  337. unset($params[$i]);
  338. if (isset($params[++$i])) {
  339. # TODO: ideally we should know if a parameter can / must have a value instead of whitelisting known valid values starting with '-' (probably only bool doesn't need a value)
  340. if ($params[$i]{0} !== '-' or $params[$i] != '-1') {
  341. $this->params[$key] = $params[$i];
  342. unset($params[$i]);
  343. } else {
  344. $i--;
  345. $this->__parseParams($params);
  346. }
  347. }
  348. } else {
  349. $this->args[] = $params[$i];
  350. unset($params[$i]);
  351. }
  352. }
  353. }
  354. }
  355. /**
  356. * Removes first argument and shifts other arguments up
  357. *
  358. * @return boolean False if there are no arguments
  359. */
  360. public function shiftArgs() {
  361. if (empty($this->args)) {
  362. return false;
  363. }
  364. unset($this->args[0]);
  365. $this->args = array_values($this->args);
  366. return true;
  367. }
  368. /**
  369. * prints help message and exits.
  370. */
  371. public function help() {
  372. $this->stdout("\nWelcome to Postfixadmin-CLI v" . $this->version);
  373. $this->stdout("---------------------------------------------------------------");
  374. $this->stdout("Usage:");
  375. $this->stdout(" postfixadmin-cli <module> <task> [--option value --option2 value]");
  376. $this->stdout("");
  377. $this->stdout("Available modules:");
  378. $modules = explode(',','admin,domain,mailbox,alias,aliasdomain,fetchmail');
  379. foreach ($modules as $module) {
  380. $this->stdout(" $module");
  381. }
  382. $this->stdout("");
  383. $this->stdout("Most modules support the following tasks:");
  384. $this->stdout(" view View an item");
  385. $this->stdout(" add Add an item");
  386. $this->stdout(" update Update an item");
  387. $this->stdout(" delete Delete an item");
  388. $this->stdout(" scheme Print database scheme (useful for developers only)");
  389. $this->stdout(" help Print help output");
  390. $this->stdout("");
  391. $this->stdout("");
  392. $this->stdout("For module-specific help, see:");
  393. $this->stdout("");
  394. $this->stdout(" postfixadmin-cli <module> help");
  395. $this->stdout(" print a detailed list of available commands");
  396. $this->stdout("");
  397. $this->stdout(" postfixadmin-cli <module> <task> help");
  398. $this->stdout(" print a list of available options.");
  399. $this->stdout("");
  400. exit();
  401. }
  402. }
  403. define ("POSTFIXADMIN_CLI", 1);
  404. $dispatcher = new PostfixAdmin($argv);
  405. $CONF = Config::read('all');
  406. $dispatcher->dispatch();
  407. /* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */