home *** CD-ROM | disk | FTP | other *** search
/ Chip 2007 January, February, March & April / Chip-Cover-CD-2007-02.iso / boot / i386 / root / usr / share / YaST2 / modules / CWMTsigKeys.ycp < prev    next >
Text File  |  2006-11-29  |  19KB  |  662 lines

  1. /**
  2.  * File:    modules/CWMTsigKeys.ycp
  3.  * Package:    Common widget manipulation, TSIG keys management widget
  4.  * Summary:    Routines for management of TSIG keys
  5.  * Authors:    Jiri Srain <jsrain@suse.cz>
  6.  *
  7.  * $Id: CWMTsigKeys.ycp 24842 2005-08-12 08:08:03Z visnov $
  8.  *
  9.  */
  10.  
  11. {
  12.  
  13. module "CWMTsigKeys";
  14. textdomain "base";
  15.  
  16. import "CWM";
  17. import "Label";
  18. import "Report";
  19. import "Popup";
  20.  
  21. // pre-declarations
  22.  
  23. global define list<string> AnalyzeTSIGKeyFile (string filename);
  24.  
  25.  
  26. // private variables
  27.  
  28. /**
  29.  * Currently configured TSIG keys
  30.  * Each entry is a map with keys "filename" and "key"
  31.  */
  32. list<map<string,string> > tsig_keys = [];
  33.  
  34. /**
  35.  * Filenames of the files that contained deleted TSIG keys
  36.  */
  37. list<string> deleted_tsig_keys = [];
  38.  
  39. /**
  40.  * Filenames of the new added TSIG keys
  41.  */
  42. list<string> new_tsig_keys = [];
  43.  
  44.  
  45. // private functions
  46.  
  47. /**
  48.  * Redraw the table of DDNS keys
  49.  */
  50. define void DdnsKeysWidgetRedraw () {
  51.     list items = maplist (map<string,string> k, tsig_keys, {
  52.     return `item (`id (k["key"]:""), k["key"]:"", k["filename"]:"");
  53.     });
  54.     UI::ChangeWidget (`id ("_cwm_delete_key"), `Enabled, size(items) > 0);
  55.     UI::ChangeWidget (`id ("_cwm_key_listing_table"), `Items, items);
  56.     UI::SetFocus (`id ("_cwm_key_listing_table"));
  57. }
  58.  
  59. /**
  60.  * Get the file that contains the specified key
  61.  * @param key string key ID
  62.  * @return string file containing the key
  63.  */
  64. define string Key2File (string key) {
  65.     string filename = "";
  66.     find (map<string,string>k, tsig_keys, {
  67.     if (k["key"]:nil == key)
  68.     {
  69.         filename = k["filename"]:"";
  70.         return true;
  71.     }
  72.     return false;
  73.     });
  74.     y2milestone ("Key: %1, File: %2", key, filename);
  75.     return filename;
  76. }
  77.  
  78. /**
  79.  * Remove file with all TSIG keys it contains
  80.  * @param filename string filename of the file with the TSIG keys
  81.  */
  82. define void RemoveTSIGKeyFile (string filename) {
  83.     new_tsig_keys = filter (string f, new_tsig_keys, {
  84.     return f != filename;
  85.     });
  86.     deleted_tsig_keys = add (deleted_tsig_keys, filename);
  87.     tsig_keys = filter (map<string,string>k, tsig_keys, {
  88.     return k["filename"]:"" != filename;
  89.     });
  90. }
  91.  
  92. /**
  93.  * Remove file containing specified TSIG key
  94.  * @param key string key ID
  95.  */
  96. define void RemoveTSIGKey (string key) {
  97.     string filename = Key2File (key);
  98.     RemoveTSIGKeyFile (filename);
  99. }
  100.  
  101. /**
  102.  * Add new file with TSIG key
  103.  * @param filename string filename of the file with the TSIG key
  104.  */
  105. define void AddTSIGKeyFile (string filename) {
  106.     deleted_tsig_keys = filter (string f, deleted_tsig_keys, {
  107.     return f != filename;
  108.     });
  109.     new_tsig_keys = add (new_tsig_keys, filename);
  110.     list<string> keys = AnalyzeTSIGKeyFile (filename);
  111.     foreach (string k, keys, {
  112.     tsig_keys = add (tsig_keys, $[
  113.         "key" : k,
  114.         "filename" : filename,
  115.     ]);
  116.     });
  117. }
  118.  
  119.  
  120. // public routines related to TSIG keys management
  121.  
  122. /**
  123.  * Remove leading and trailibg blanks and quotes from file name
  124.  * @param filename string file name
  125.  * @return file name without leading/trailing quotes and blanks
  126.  */
  127. global define string NormalizeFilename (string filename) {
  128.     while (filename != "" && (substring (filename, 0, 1) == " "
  129.     || substring (filename, 0, 1) == "\""))
  130.     {
  131.     filename = substring (filename, 1);
  132.     }
  133.     while (filename != ""
  134.         && (substring (filename, size (filename) - 1, 1) == " "
  135.         || substring (filename, size (filename) - 1, 1) == "\""))
  136.     {
  137.     filename = substring (filename, 0, size (filename) - 1);
  138.     }
  139.     return filename;
  140. }
  141.  
  142. /**
  143.  * Analyze file that may contain TSIG keys
  144.  * @param filename string filename of the file that may contain TSIG keys
  145.  * @return a list of all TSIG key IDs in the file
  146.  */
  147. global define list<string> AnalyzeTSIGKeyFile (string filename) {
  148.     filename = NormalizeFilename (filename);
  149.     string contents = (string)SCR::Read (.target.string, filename);
  150.     if (contents == nil)
  151.     {
  152.     y2warning ("Unable to read file with TSIG keys: %1", filename);
  153.         return [];
  154.     }
  155.     list<string> ret = [];
  156.     list<string> parts = splitstring (contents, "{}");
  157.     foreach (string p, parts, {
  158.     if (regexpmatch (p, ".*key[[:space:]]+[^[:space:]}{;]+\\.* $"))
  159.     {
  160.         ret = add (ret,
  161.         regexpsub (p, ".*key[[:space:]]+([^[:space:]}{;]+)\\.* $", "\\1"));
  162.     }
  163.     });
  164.     y2milestone ("File: %1, Keys: %2", filename, ret);
  165.     return ret;
  166. }
  167.  
  168. /**
  169.  * Remove all 3 files holding the TSIG key data
  170.  * @param main string filename of the main file
  171.  */
  172. global define void DeleteTSIGKeyFromDisk (string main) {
  173.     list<string> keys = AnalyzeTSIGKeyFile (main);
  174.     y2milestone ("Removing file %1, found keys: %2", main, keys);
  175.     foreach (string k, keys, {
  176.     SCR::Execute (.target.bash, sformat (
  177.         "rm -f /etc/named.d/K%1\\.* ",
  178.         tolower (k)));
  179.     });
  180.     SCR::Execute (.target.remove, main);
  181. }
  182.  
  183. /**
  184.  * Transformate the list of files to the list of TSIG key description maps
  185.  * @param filenames a list of file names of the TSIG keys
  186.  * @return a list of TSIG key describing maps
  187.  */
  188. global define list<map<string,string> > Files2KeyMaps (list<string> filenames) {
  189.     list<list<map<string,string> > > tmpret = maplist (string f, filenames, {
  190.     list<string> keys = AnalyzeTSIGKeyFile (f);
  191.     return maplist (string k, keys, {
  192.         return $[
  193.         "filename" : f,
  194.         "key" : k,
  195.         ];
  196.     });
  197.     });
  198.     list<map<string,string> > ret = (list<map<string, string> >)
  199.     flatten (tmpret);
  200.     y2milestone ("Files: %1, Keys: %2", filenames, ret);
  201.     return ret;
  202. }
  203.  
  204. /**
  205.  * Get all TSIG keys that present in the files
  206.  * @param filename a list of file names
  207.  * @return a list of all TSIG key IDs
  208.  */
  209. global define list<string> Files2Keys (list<string> filenames) {
  210.     list<map<string,string> > keys = Files2KeyMaps (filenames);
  211.     list<string> ret = maplist (map<string,string> k, keys, {
  212.     return k["key"]:"";
  213.     });
  214.     y2milestone ("Files: %1, Keys: %2", filenames, ret);
  215.     return ret;
  216. }
  217.  
  218.  
  219. // widget related functions
  220.  
  221. /**
  222.  * Init function of the widget
  223.  * @param map widget a widget description map
  224.  * @param key strnig the widget key
  225.  */
  226. global define void Init (map<string,any> widget, string key) {
  227.     map<string,any>() get_keys_info = (map<string,any>())
  228.     widget["get_keys_info"]:nil;
  229.     map<string,any> info = get_keys_info ();
  230.     tsig_keys = info["tsig_keys"]:[];
  231.     deleted_tsig_keys = info["removed_files"]:[];
  232.     new_tsig_keys = info["new_files"]:[];
  233.     if (! haskey (info, "tsig_keys"))
  234.     {
  235.     list<string> files = info["key_files"]:[];
  236.     tsig_keys = Files2KeyMaps (files);
  237.     }
  238.     string initial_path = "/etc/named.d/";
  239.     UI::ChangeWidget (`id ("_cwm_existing_key_file"), `Value, initial_path);
  240.     UI::ChangeWidget (`id ("_cwm_new_key_file"), `Value, initial_path);
  241.     UI::ChangeWidget (`id ("_cwm_new_key_id"), `ValidChars,
  242.     "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_");
  243.     DdnsKeysWidgetRedraw ();
  244. }
  245.  
  246. /**
  247.  * Handle function of the widget
  248.  * @param map widget a widget description map
  249.  * @param key strnig the widget key
  250.  * @param event map event to be handled
  251.  * @return symbol for wizard sequencer or nil
  252.  */
  253. global define symbol Handle (map<string,any> widget, string key, map event) {
  254.     any ret = event["ID"]:nil;
  255.     string existing_filename = (string)
  256.     UI::QueryWidget (`id ("_cwm_existing_key_file"), `Value);
  257.     string new_filename = (string)
  258.     UI::QueryWidget (`id ("_cwm_new_key_file"), `Value);
  259.     if (ret == "_cwm_delete_key")
  260.     {
  261.     string key = (string)UI::QueryWidget (`id ("_cwm_key_listing_table"),
  262.         `CurrentItem);
  263.     string delete_filename = Key2File (key);
  264.     if (widget["list_used_keys"]:nil != nil
  265.         && is (widget["list_used_keys"]:nil, list<string>()))
  266.     {
  267.         list<string>() lister = (list<string>())
  268.         widget["list_used_keys"]:nil;
  269.         list<string> used_keys = lister ();
  270.         list<string> keys_to_delete = AnalyzeTSIGKeyFile (delete_filename);
  271.         keys_to_delete = filter (string k, keys_to_delete, {
  272.         return contains (used_keys, k);
  273.         });
  274.         if (size (keys_to_delete) > 0)
  275.         {
  276.         string message = mergestring (keys_to_delete, ", ");
  277.         // popup message
  278.         message = _("The selected TSIG key cannot be deleted,
  279. because it is in use.
  280. Stop using it in the configuration first.");
  281.         // popup title
  282.         Popup::AnyMessage (_("Cannot delete TSIG key."), message);
  283.         return nil;
  284.         }
  285.     }
  286.     RemoveTSIGKeyFile (delete_filename);
  287.     }
  288.     else if (ret == "_cwm_browse_existing_key_file")
  289.     {
  290.     existing_filename = (string)UI::AskForExistingFile (
  291.         existing_filename,
  292.         "",
  293.         // popup headline
  294.         _("Select File with the Authentication Key"));
  295.     if (existing_filename != nil)
  296.     {
  297.         UI::ChangeWidget (`id ("_cwm_existing_key_file"), `Value,
  298.         existing_filename);
  299.     }
  300.     return nil;
  301.     }
  302.     else if (ret == "_cwm_browse_new_key_file")
  303.     {
  304.     new_filename = (string)UI::AskForSaveFileName (
  305.         new_filename,
  306.         "",
  307.         // popup headline
  308.         _("Select File for the Authentication Key"));
  309.         if (new_filename != nil)
  310.             UI::ChangeWidget (`id ("_cwm_new_key_file"), `Value, new_filename);
  311.         return nil;
  312.     }
  313.     else if (ret == "_cwm_generate_key")
  314.     {
  315.     string key = (string)UI::QueryWidget (`id ("_cwm_new_key_id"), `Value);
  316.     map stat = (map)SCR::Read (.target.stat, new_filename);
  317.     if (size (stat) != 0)
  318.     {
  319.         if (stat["isdir"]:false)
  320.         {
  321.         UI::SetFocus (`id ("new_key_file"));
  322.                 Report::Error (
  323.             // error report
  324.             _("Specified filename is an existing directory."));
  325.         return nil;
  326.         }
  327.         // yes-no popup
  328.         if (! Popup::YesNo (_("Specified file exists. Rewrite it?")))
  329.         {
  330.         return nil;
  331.         }
  332.         else
  333.         {
  334.         DeleteTSIGKeyFromDisk (new_filename);
  335.         RemoveTSIGKeyFile (new_filename);
  336.         }
  337.     }
  338.     if (key == nil || key == "")
  339.     {
  340.         UI::SetFocus (`id ("new_key_name"));
  341.         // error report
  342.         Popup::Error (_("The TSIG key ID was not specified."));
  343.         return nil;
  344.     }
  345.     // specified key exists
  346.     if (Key2File (key) != "")
  347.     {
  348.         // yes-no popup
  349.         if (! Popup::YesNo (_("The key with the specified ID exists and is used.
  350. Remove it?")))
  351.         {
  352.         return nil;
  353.         }
  354.         else
  355.         {
  356.         string remove_file = Key2File (key);
  357.         DeleteTSIGKeyFromDisk (remove_file);
  358.         RemoveTSIGKeyFile (remove_file);
  359.         }
  360.     }
  361.     // specified key is present on the disk, but not used
  362.     if (0 == SCR::Execute (.target.bash, sformat (
  363.         "ls /etc/named.d/K%1\\.*",
  364.         tolower (key))))
  365.     {
  366.         // yes-no popup
  367.         if (Popup::YesNo (_("A key with the specified ID was found
  368. on your disk. Remove it?")))
  369.         {
  370.         SCR::Execute (.target.bash, sformat (
  371.             "rm -f `ls /etc/named.d/K%1\\.*`",
  372.             tolower (key)));
  373.         list<string> files = (list<string>)SCR::Read (
  374.             .target.dir,
  375.             "/etc/named.d");
  376.         foreach (string f, files, {
  377.             if ( contains (AnalyzeTSIGKeyFile (f), key))
  378.             DeleteTSIGKeyFromDisk (f);
  379.         });
  380.         }
  381.     }
  382.  
  383.     // yes-no popup
  384.     if (! Popup::YesNo (_("The key will be created now. Continue?")))
  385.         return nil;
  386.     SCR::Execute (.target.bash,
  387.         "test -d /etc/named.d || mkdir /etc/named.d");
  388.     string gen_command = sformat (
  389.         "/usr/bin/genDDNSkey --force  -f %1 -n %2 -d /etc/named.d",
  390.         new_filename, key);
  391.     y2milestone ("Running %1", gen_command);
  392.     integer gen_ret = (integer)SCR::Execute (.target.bash, gen_command);
  393.     if (gen_ret != 0)
  394.     {
  395.         // error report
  396.         Report::Error (_("Creating the TSIG key failed."));
  397.         return nil;
  398.     }
  399.     ret = "_cwm_add_key";
  400.     existing_filename = new_filename;
  401.     }
  402.     if (ret == "_cwm_add_key")
  403.     {
  404.     map stat = (map)SCR::Read (.target.stat, new_filename);
  405.     if (size (stat) == 0)
  406.     {
  407.         // message popup
  408.         Popup::Message (_("The specified file does not exist."));
  409.         return nil;
  410.     }
  411.     list<string> keys = AnalyzeTSIGKeyFile (existing_filename);
  412.     if (size (keys) == 0)
  413.     {
  414.         // message popup
  415.         Popup::Message (_("The specified file does not contain any TSIG key."));
  416.         return nil;
  417.     }
  418.     list<string> coliding_files = maplist (string k, keys, {
  419.         return Key2File (k);
  420.     });
  421.     coliding_files = filter (string f, toset (coliding_files), {
  422.         return f != "";
  423.     });
  424.     if (size (coliding_files) > 0)
  425.     {
  426.         // yes-no popup
  427.         if (!Popup::YesNo (_("The specified file contains a TSIG key with the same
  428. identifier as some of already present keys.
  429. Old keys will be removed. Continue?")))
  430.         {
  431.         return nil;
  432.         }
  433.         else
  434.         {
  435.         foreach (string f, coliding_files, {
  436.             RemoveTSIGKeyFile (f);
  437.         });
  438.         }
  439.     }
  440.     AddTSIGKeyFile (existing_filename);
  441.     }
  442.     DdnsKeysWidgetRedraw ();
  443.     return nil;
  444. }
  445.  
  446. /**
  447.  * Store function of the widget
  448.  * @param map widget a widget description map
  449.  * @param key strnig the widget key
  450.  * @param event map that caused widget data storing
  451.  */
  452. global define void Store (map<string,any> widget, string key, map event) {
  453.     void(map<string,any>) set_info = (void (map<string,any>))
  454.     widget["set_keys_info"]:nil;
  455.     map<string,any> info = $[
  456.     "removed_files" : deleted_tsig_keys,
  457.     "new_files" : new_tsig_keys,
  458.     "tsig_keys" : tsig_keys,
  459.     "key_files" : toset (maplist (map<string,string> k, tsig_keys, {
  460.         return k["filename"]:"";
  461.     })),
  462.     ];
  463.     set_info (info);
  464. }
  465.  
  466. /**
  467.  * Store function of the widget
  468.  * @param map widget a widget description map
  469.  * @param key strnig the widget key
  470.  * @param event map that caused widget data storing/**
  471.  * Init function of the widget
  472.  * @param key strnig the widget key
  473.  */
  474. global define void InitWrapper (string key) {
  475.     Init (CWM::GetProcessedWidget (), key);
  476. }
  477.  
  478. /**
  479.  * Handle function of the widget
  480.  * @param map widget a widget description map
  481.  * @param key strnig the widget key
  482.  * @param event map event to be handled
  483.  * @return symbol for wizard sequencer or nil
  484.  */
  485. global define symbol HandleWrapper (string key, map event) {
  486.     return Handle (CWM::GetProcessedWidget (), key, event);
  487. }
  488.  
  489. /**
  490.  * Store function of the widget
  491.  * @param key strnig the widget key
  492.  * @param event map that caused widget data storing
  493.  */
  494. global define void StoreWrapper (string key, map event) {
  495.     Store (CWM::GetProcessedWidget (), key, event);
  496. }
  497.  
  498. /**
  499.  * Get the widget description map
  500.  * @param settings a map of all parameters needed to create the widget properly
  501.  * <pre>
  502.  * "get_keys_info" : map<string,any>() -- function for getting information
  503.  *          about TSIG keys. Return map should contain:
  504.  *           - "removed_files" : list<string> -- files that have been removed
  505.  *           - "new_files" : list<string> -- files that have been added
  506.  *           - "tsig_keys" : list<map<string,string>> -- list of all TSIG keys
  507.  *           - "key_files" : list<string> -- list of all files that may contain
  508.  *                       TSIG keys
  509.  *           Either "tsig_keys" or "key_files" are mandatory
  510.  * "set_keys_info" : void (map<string,any>) -- function for storing information
  511.  *          about keys. Map has keys:
  512.  *           - "removed_files" : list<string> -- files that have been removed
  513.  *           - "new_files" : list<string> -- files that have been added
  514.  *           - "tsig_keys" : list<map<string,string>> -- list of all TSIG keys
  515.  *           - "key_files" : list<string> -- list of all files that contain
  516.  *                       TSIG keys
  517.  *
  518.  * Additional settings:
  519.  * - "list_used_keys" : list<string>() -- function for getting the list of
  520.  *          used TSIG keys. The list is used to prevent used TSIG keys from
  521.  *          being deleted. If not present, all keys may get deleted.
  522.  * - "help" : string -- help to the whole widget. If not specified, generic help
  523.  *          is used (button labels are patched correctly)
  524.  * </pre>
  525.  * @return a map the widget description map
  526.  */
  527. global define map<string,any> CreateWidget (map<string,any> settings) {
  528.     // tsig keys management dialog help 1/4
  529.     string help = _("<p><big><b>TSIG Key Management</b></big><br>
  530. Use this dialog to manage the TSIG keys.</p>
  531. ")
  532.     +
  533.     // tsig keys management dialog help 2/4
  534.     _("<p><big><b>Adding an Existing TSIG Key</b></big><br>
  535. To add an already created TSIG key, select a <b>Filename</b> of the file
  536. containing the key and click <b>Add</b>.</p>
  537. ")
  538.     +
  539.     // tsig keys management dialog help 3/4
  540.     _("<p><big><b>Creating a New TSIG Key</b></big><br>
  541. To create a new TSIG key, set the <b>Filename</b> of the file in which to
  542. create the key and the <b>Key ID</b> to identify the key then click
  543. <b>Generate</b>.</p>
  544. ")
  545.     +
  546.     // tsig keys management dialog help 4/4
  547.     _("<p><big><b>Removing a TSIG Key</b></big><br>
  548. To remove a configured TSIG key, select it and click <b>Delete</b>.
  549. All keys in the same file are deleted.
  550. If a TSIG key is in use in the configuration
  551. of the server, it cannot be deleted. The server must stop using it
  552. in the configuration first.</p>
  553. ");
  554.  
  555.     term add_existing = `VSquash (
  556.     // Frame label - adding a created server key
  557.     `Frame (_("Add an Existing TSIG Key"),
  558.         `HBox (
  559.         `HWeight (9,
  560.             `HBox (
  561.             `HWeight (7,
  562.                 `HBox (
  563.                 `TextEntry (`id ("_cwm_existing_key_file"), `opt (`hstretch),
  564.                 // text entry
  565.                 Label::FileName ())
  566.                 )
  567.             ),
  568.             `HWeight (2,
  569.                 `HBox (
  570.                 `VBox (
  571.                     `Label (" "), `PushButton (`id ("_cwm_browse_existing_key_file"),
  572.                     Label::BrowseButton ())
  573.                 )
  574.                 )
  575.             )
  576.             )
  577.         ),
  578.         `HWeight (2,
  579.             `Bottom (
  580.             `VSquash (`PushButton (`id ("_cwm_add_key"), `opt (`hstretch), Label::AddButton ()))
  581.             )
  582.         )
  583.         )
  584.     )
  585.     );
  586.  
  587.     term create_new = `VSquash (
  588.     // Frame label - creating a new server key
  589.     `Frame (_("Create a New TSIG Key"),
  590.         `HBox (
  591.         `HWeight (9,
  592.             `HBox (
  593.             `HWeight (7,
  594.                 `HBox (
  595.                 // text entry
  596.                 `TextEntry (`id ("_cwm_new_key_id"), `opt (`hstretch), _("&Key ID")),
  597.                 // text entry
  598.                 `TextEntry (`id ("_cwm_new_key_file"), `opt (`hstretch), Label::FileName())
  599.                 )
  600.             ),
  601.             `HWeight (2,
  602.                 `HBox (
  603.                 `VBox (
  604.                     `Label (" "),
  605.                     `PushButton (`id ("_cwm_browse_new_key_file"), Label::BrowseButton ())
  606.                 )
  607.                 )
  608.             )
  609.             )
  610.         ),
  611.         `HWeight (2,
  612.             `Bottom (
  613.             // push button
  614.             `VSquash (`PushButton ( `id ("_cwm_generate_key"), `opt(`hstretch), _("&Generate")))
  615.             )
  616.         )
  617.         )
  618.     )
  619.     );
  620.  
  621.     term current_keys = `VBox (
  622.     `VSpacing (0.5),
  623.     // Table header - in fact label
  624.     `Left (`Label (_("Current TSIG Keys"))),
  625.     `HBox (
  626.         `HWeight (9, `Table (`id ("_cwm_key_listing_table"), `header (
  627.                                     // Table header item - DNS key listing
  628.                                     _("Key ID"),
  629.                                     // Table header item - DNS key listing
  630.                                     _("Filename")
  631.                                 ), []
  632.         )),
  633.        `HWeight (2, `VBox (
  634.         `VSquash (`PushButton (`id ("_cwm_delete_key"),
  635.             `opt (`hstretch),
  636.             Label::DeleteButton ())),
  637.         `VStretch ()
  638.         ))
  639.     )
  640.     );
  641.  
  642.     term contents = `VBox (
  643.     add_existing,
  644.     create_new,
  645.     current_keys
  646.     );
  647.  
  648.     map<string,any> ret = (map<string,any>)union ($[
  649.     "widget" : `custom,
  650.     "custom_widget" : contents,
  651.     "help" : help,
  652.     "init" : InitWrapper,
  653.     "store" : StoreWrapper,
  654.     "handle" : HandleWrapper,
  655.     ], settings);
  656.  
  657.     return ret;
  658. }
  659.  
  660. // EOF
  661. }
  662.