QString CompilerDriver_junosacl::assembleFwScript(Cluster *cluster,
                                                  Firewall *fw,
                                                  bool cluster_member,
                                                  OSConfigurator *oscnf)
{
    Configlet script_skeleton(fw, "junos", "script_skeleton");
    Configlet top_comment(fw, "junos", "top_comment");

    script_skeleton.setVariable("system_configuration_script",
                                QString::fromUtf8(system_configuration_script.c_str()));
    script_skeleton.setVariable("policy_script",
                                QString::fromUtf8(policy_script.c_str()));

    FWOptions* options = fw->getOptionsObject();
    options->setStr("prolog_script", options->getStr("junosacl_prolog_script"));
    options->setStr("epilog_script", options->getStr("junosacl_epilog_script"));

    // we do not offer user a choice of the place where to put prolog
    // lines, therefore we can reset this attribute to make sure it
    // does not interfere
    options->setStr("prolog_place", "");

    assembleFwScriptInternal(cluster, fw, cluster_member,
                             oscnf, &script_skeleton, &top_comment, "!", true);
    return script_skeleton.expand();
}
예제 #2
0
/*
 * store all data in the object
 */
void pixosIfaceOptsDialog::accept()
{
    // validate user input before saving
    if (!validate())  return;

    ProjectPanel *project = mw->activeProject();
    std::unique_ptr<FWCmdChange> cmd( new FWCmdChange(project, obj));

    // new_state  is a copy of the interface object
    FWObject* new_state = cmd->getNewState();
    FWOptions* ifopt = Interface::cast(new_state)->getOptionsObject();
    assert(ifopt!=NULL);

    if (cluster_interface)
    {
        ifopt->setStr("type", "cluster_interface");
    } else
    {
        QString new_type = m_dialog->iface_type->itemData(
            m_dialog->iface_type->currentIndex()).toString();
        ifopt->setStr("type", new_type.toStdString());
    }

    data.saveAll(ifopt);

    if (!cmd->getOldState()->cmp(new_state, true))
        project->undoStack->push(cmd.release());
    
    QDialog::accept();
}
예제 #3
0
void Importer::setInterfaceVlanId(const std::string &vlan_id)
{
    if (current_interface!=NULL)
    {
        FWOptions *ifopt = (Interface::cast(current_interface))->getOptionsObject();
        ifopt->setStr("type", "8021q");
        ifopt->setStr("vlan_id", vlan_id);
    }
}
예제 #4
0
void PFImporter::addLogging()
{
    PolicyRule *rule = PolicyRule::cast(current_rule);
    FWOptions *ropt = rule->getOptionsObject();

    /*
      alerts         Immediate action needed           (severity=1)
      critical       Critical conditions               (severity=2)
      debugging      Debugging messages                (severity=7)
      disable        Disable log option on this ACL element, (no log at all)
      emergencies    System is unusable                (severity=0)
      errors         Error conditions                  (severity=3)
      inactive       Keyword for disabling an ACL element
      informational  Informational messages            (severity=6)
      interval       Configure log interval, default value is 300 sec
      notifications  Normal but significant conditions (severity=5)
      warnings       Warning conditions                (severity=4)
    */
    QMap<QString, QString> logging_levels;

    logging_levels["alerts"] = "alert";
    logging_levels["critical"] = "crit";
    logging_levels["debugging"] = "debug";
    logging_levels["emergencies"] = "";
    logging_levels["errors"] = "error";
    logging_levels["informational"] = "info";
    logging_levels["notifications"] = "notice";
    logging_levels["warnings"] = "warning";
    logging_levels["0"] = "";
    logging_levels["1"] = "alert";
    logging_levels["2"] = "crit";
    logging_levels["3"] = "error";
    logging_levels["4"] = "warning";
    logging_levels["5"] = "notice";
    logging_levels["6"] = "info";
    logging_levels["7"] = "debug";

    // QStringList log_levels = getLogLevels("pix");

    rule->setLogging(logging);

    QString log_level_qs = log_level.c_str();
    if ( ! log_level_qs.isEmpty())
    {
        if (logging_levels.count(log_level_qs) != 0)
            ropt->setStr("log_level", logging_levels[log_level_qs].toStdString());
        else
            ropt->setStr("log_level", log_level);

        if (log_level_qs == "disable" || log_level_qs == "inactive")
            ropt->setBool("disable_logging_for_this_rule", true);
    }

    if ( ! log_interval.empty())
    {
        bool ok = false;
        int log_interval_int = QString(log_interval.c_str()).toInt(&ok);
        if (ok)
            ropt->setInt("log_interval", log_interval_int);
    }
}
예제 #5
0
QString CompilerDriver_pix::assembleFwScript(Cluster *cluster,
                                             Firewall* fw,
                                             bool cluster_member,
                                             OSConfigurator *oscnf)
{
    Configlet script_skeleton(fw, "pix_os", "script_skeleton");
    Configlet top_comment(fw, "pix_os", "top_comment");

    FWOptions* options = fw->getOptionsObject();
    options->setStr("prolog_script", options->getStr("pix_prolog_script"));
    options->setStr("epilog_script", options->getStr("pix_epilog_script"));
    options->setStr("prolog_place", "");

    string vers = fw->getStr("version");
    string platform = fw->getStr("platform");

    bool outbound_acl_supported =
        Resources::platform_res[platform]->getResourceBool(
            string("/FWBuilderResources/Target/options/")+
            "version_"+vers+
            "/pix_outbound_acl_supported");

    bool afpa = options->getBool("pix_assume_fw_part_of_any");
    bool emulate_outb_acls = options->getBool("pix_emulate_out_acl");
    bool generate_outb_acls = options->getBool("pix_generate_out_acl");

    top_comment.setVariable(
        "outbound_acl_supported",
        QString((outbound_acl_supported) ? "supported" : "not supported"));

    top_comment.setVariable("emulate_outb_acls",
                            QString((emulate_outb_acls)?"yes":"no"));

    top_comment.setVariable("generate_outb_acls",
                            QString((generate_outb_acls)?"yes":"no"));

    top_comment.setVariable("afpa", QString((afpa)?"yes":"no"));

    script_skeleton.setVariable("short_script", options->getBool("short_script"));

    script_skeleton.setVariable("not_short_script",
                                ! options->getBool("short_script"));

    script_skeleton.setVariable("preamble_commands", 
                                QString::fromUtf8(
                                    preamble_commands.c_str()));

    script_skeleton.setVariable("clear_commands", 
                                QString::fromUtf8(
                                    clear_commands.c_str()));

    script_skeleton.setVariable("system_configuration_script", 
                                QString::fromUtf8(
                                    system_configuration_script.c_str()));

    script_skeleton.setVariable("named_objects_and_object_groups",
                                QString::fromUtf8(
                                    named_objects_and_groups.c_str()));

    script_skeleton.setVariable("policy_script",
                                QString::fromUtf8(policy_script.c_str()));
    script_skeleton.setVariable("nat_script",
                                QString::fromUtf8(nat_script.c_str()));
    script_skeleton.setVariable("routing_script",
                                QString::fromUtf8(routing_script.c_str()));

    assembleFwScriptInternal(cluster, fw, cluster_member, oscnf,
                             &script_skeleton, &top_comment, "!", true);

    return script_skeleton.expand();
}
예제 #6
0
void RuleOptionsDialog::loadFWObject(FWObject *o)
{
    obj = o;
    firewall = o;
    // use Firewall::cast to match both Firewall and Cluster
    while (!Firewall::cast(firewall)) firewall = firewall->getParent();
    platform = firewall->getStr("platform").c_str();
    string version = firewall->getStr("version");

    // build a map for combobox so visible combobox items can be localized
    QStringList route_options = getRouteOptions_pf_ipf(platform);
    QStringList route_load_options = getRouteLoadOptions_pf(platform);
    QStringList classify_options_ipfw = getClassifyOptions_ipfw(platform);

    Rule      *rule = dynamic_cast<Rule*>(o);
    FWOptions *ropt = rule->getOptionsObject();
    PolicyRule *policy_rule = PolicyRule::cast(rule);

    int wid=0;
    if (platform=="iptables") wid=1;
    if (platform=="ipf")      wid=2;
    if (platform=="pf")       wid=3;
    if (platform=="ipfw")     wid=4;
    if (platform=="pix" || platform=="fwsm")      wid=5;
    if (platform=="iosacl" || platform=="procurve_acl")   wid=6;
	if (platform=="junosacl") wid=7;

    m_dialog->wStack->widget(wid)->raise();
    m_dialog->wStack->setCurrentWidget(m_dialog->wStack->widget(wid));

    QStringList  logLevels=getLogLevels( obj->getStr("platform").c_str() );
    m_dialog->ipt_logLevel->clear();
    m_dialog->ipt_logLevel->addItems(getScreenNames(logLevels));
    m_dialog->ipf_logLevel->clear();
    m_dialog->ipf_logLevel->addItems(getScreenNames(logLevels));
    m_dialog->pix_logLevel->clear();
    m_dialog->pix_logLevel->addItems(getScreenNames(logLevels));

    QStringList logFacilities=getLogFacilities( obj->getStr("platform").c_str());
    m_dialog->ipf_logFacility->clear();
    m_dialog->ipf_logFacility->addItems(getScreenNames(logFacilities));
    QStringList limitSuffixes=getLimitSuffixes( obj->getStr("platform").c_str());
    m_dialog->ipt_limitSuffix->clear();
    m_dialog->ipt_limitSuffix->addItems(getScreenNames(limitSuffixes));

    m_dialog->ipt_hashlimit_suffix->clear();
    m_dialog->ipt_hashlimit_suffix->addItems(getScreenNames(limitSuffixes));

    fillInterfaces(m_dialog->ipt_iif);
    fillInterfaces(m_dialog->ipt_oif);
    fillInterfaces(m_dialog->ipf_route_opt_if);
    fillInterfaces(m_dialog->pf_route_opt_if);


    data.clear();

    if (platform=="iptables")
    {
        data.registerOption(m_dialog->ipt_logPrefix, ropt,  "log_prefix");
        data.registerOption(m_dialog->ipt_logLevel, ropt,
                             "log_level", logLevels);
        data.registerOption(m_dialog->ipt_nlgroup, ropt,  "ulog_nlgroup");

        data.registerOption(m_dialog->ipt_limit, ropt,  "limit_value");
        data.registerOption(m_dialog->ipt_limitSuffix, ropt,
                             "limit_suffix", limitSuffixes);
        data.registerOption(m_dialog->ipt_limit_not, ropt,  "limit_value_not");
        data.registerOption(m_dialog->ipt_burst, ropt,  "limit_burst");

        data.registerOption(m_dialog->ipt_connlimit, ropt,  "connlimit_value");
        data.registerOption(m_dialog->ipt_connlimit_above_not, ropt,
                            "connlimit_above_not");
        data.registerOption(m_dialog->ipt_connlimit_masklen, ropt,
                            "connlimit_masklen");

        data.registerOption(m_dialog->ipt_hashlimit, ropt,  "hashlimit_value");
        data.registerOption(m_dialog->ipt_hashlimit_suffix, ropt,
                            "hashlimit_suffix");
        data.registerOption(m_dialog->ipt_hashlimit_burst, ropt,
                            "hashlimit_burst");
        data.registerOption(m_dialog->cb_srcip, ropt,  "hashlimit_mode_srcip");
        data.registerOption(m_dialog->cb_dstip, ropt,  "hashlimit_mode_dstip");
        data.registerOption(m_dialog->cb_srcport, ropt,
                            "hashlimit_mode_srcport");
        data.registerOption(m_dialog->cb_dstport, ropt,
                            "hashlimit_mode_dstport");
        data.registerOption(m_dialog->ipt_hashlimit_dstlimit, ropt,
                            "hashlimit_dstlimit");
        data.registerOption(m_dialog->ipt_hashlimit_name, ropt,
                            "hashlimit_name");
        data.registerOption(m_dialog->ipt_hashlimit_size, ropt,
                            "hashlimit_size");
        data.registerOption(m_dialog->ipt_hashlimit_max, ropt,
                            "hashlimit_max");
        data.registerOption(m_dialog->ipt_hashlimit_expire, ropt,
                            "hashlimit_expire");
        data.registerOption(m_dialog->ipt_hashlimit_gcinterval, ropt,
                            "hashlimit_gcinterval");

        // in v3.0 attribute "assume fw is part of any" used to be a
        // checkbox and therefore stored as boolean in the rule
        // options. Old "on" maps to the new "on", which means old "True"
        // maps to "1". Old "off" maps to "use global" though.
        string old_val = ropt->getStr("firewall_is_part_of_any_and_networks");
        if (old_val == "True") ropt->setStr("firewall_is_part_of_any_and_networks", "1");
        if (old_val == "False") ropt->setStr("firewall_is_part_of_any_and_networks", "");

        QStringList threeStateMapping;
        threeStateMapping.push_back(QObject::tr("Follow global setting"));
        threeStateMapping.push_back("");

        threeStateMapping.push_back(QObject::tr("On"));
        threeStateMapping.push_back("1");

        threeStateMapping.push_back(QObject::tr("Off"));
        threeStateMapping.push_back("0");

        data.registerOption(m_dialog->ipt_assume_fw_is_part_of_any, ropt,
                            "firewall_is_part_of_any_and_networks",
                            threeStateMapping);
        data.registerOption(m_dialog->ipt_stateless, ropt,  "stateless");

        data.registerOption(m_dialog->ipt_mark_connections, ropt,
                            "ipt_mark_connections");

        data.registerOption(m_dialog->classify_str, ropt, "classify_str");

        // Route
        data.registerOption(m_dialog->ipt_iif, ropt, "ipt_iif" );
        data.registerOption(m_dialog->ipt_oif, ropt, "ipt_oif" );
        data.registerOption(m_dialog->ipt_gw, ropt, "ipt_gw" );
        data.registerOption(m_dialog->ipt_continue, ropt, "ipt_continue" );
        data.registerOption(m_dialog->ipt_tee, ropt, "ipt_tee");

        FWObject *o = policy_rule->getTagObject();
        m_dialog->iptTagDropArea->setObject(o);
        m_dialog->iptTagDropArea->update();
    }


    if (platform=="ipf")
    {
        data.registerOption(m_dialog->ipf_logFacility, ropt,
                            "ipf_log_facility", logFacilities);
        data.registerOption(m_dialog->ipf_logLevel, ropt,
                            "log_level", logLevels);
        data.registerOption(m_dialog->ipf_masq_icmp, ropt,
                            "ipf_return_icmp_as_dest");
        data.registerOption(m_dialog->ipf_stateless, ropt,  "stateless");
        data.registerOption(m_dialog->ipf_keep_frags, ropt,  "ipf_keep_frags");

        // Route
        data.registerOption(m_dialog->ipf_route_option, ropt,
                            "ipf_route_option", route_options);
        data.registerOption(m_dialog->ipf_route_opt_if, ropt,
                            "ipf_route_opt_if");
        data.registerOption(m_dialog->ipf_route_opt_addr, ropt,
                            "ipf_route_opt_addr");
    }

    if (platform=="pf")
    {
        bool ge_4_5 = XMLTools::version_compare(version, "4.5")>=0;

        m_dialog->pf_no_sync->setEnabled(ge_4_5);
        m_dialog->pf_pflow->setEnabled(ge_4_5);
        
        data.registerOption(m_dialog->pf_logPrefix, ropt,
                            "log_prefix");
        data.registerOption(m_dialog->pf_stateless, ropt,
                            "stateless");
        data.registerOption(m_dialog->pf_keep_state, ropt,
                            "pf_keep_state");
        data.registerOption(m_dialog->pf_no_sync, ropt,
                            "pf_no_sync");
        data.registerOption(m_dialog->pf_pflow, ropt,
                            "pf_pflow");
        data.registerOption(m_dialog->pf_sloppy_tracker, ropt,
                            "pf_sloppy_tracker");
        data.registerOption(m_dialog->pf_rule_max_state, ropt,
                            "pf_rule_max_state");
        data.registerOption(m_dialog->pf_source_tracking, ropt,
                            "pf_source_tracking");
        data.registerOption(m_dialog->pf_max_src_nodes, ropt,
                            "pf_max_src_nodes");
        data.registerOption(m_dialog->pf_max_src_states, ropt,
                            "pf_max_src_states");
        data.registerOption(m_dialog->pf_max_src_conn, ropt,
                            "pf_max_src_conn");
        data.registerOption(m_dialog->pf_overload_table, ropt,
                            "pf_max_src_conn_overload_table");
        data.registerOption(m_dialog->pf_flush, ropt,
                            "pf_max_src_conn_flush");
        data.registerOption(m_dialog->pf_global, ropt,
                            "pf_max_src_conn_global");
        data.registerOption(m_dialog->pf_max_src_conn_rate_num, ropt,
                            "pf_max_src_conn_rate_num");
        data.registerOption(m_dialog->pf_max_src_conn_rate_seconds, ropt,
                            "pf_max_src_conn_rate_seconds");
        data.registerOption(m_dialog->pf_modulate, ropt,
                            "pf_modulate_state");
        data.registerOption(m_dialog->pf_synproxy, ropt,
                            "pf_synproxy");

        // Tag
        FWObject *o = policy_rule->getTagObject();
        m_dialog->pfTagDropArea->setObject(o);
        m_dialog->pfTagDropArea->update();

        // Classify
        data.registerOption(m_dialog->pf_classify_str, ropt, "pf_classify_str");

        // Route
        data.registerOption(m_dialog->pf_fastroute, ropt, "pf_fastroute");
        data.registerOption(m_dialog->pf_route_load_option, ropt,
                            "pf_route_load_option", route_load_options);
        data.registerOption(m_dialog->pf_route_option, ropt, "pf_route_option",
                            route_options);
        data.registerOption(m_dialog->pf_route_opt_if, ropt, "pf_route_opt_if");
        data.registerOption(m_dialog->pf_route_opt_addr, ropt, "pf_route_opt_addr");
    }

    if (platform=="ipfw")
    {
        data.registerOption(m_dialog->ipfw_stateless, ropt, "stateless");

/* #2367 */
        // Classify
        data.registerOption(m_dialog->ipfw_classify_method, ropt,
                            "ipfw_classify_method", classify_options_ipfw);
        data.registerOption(m_dialog->usePortNum, ropt, "ipfw_pipe_queue_num");
    }

    if (platform=="iosacl" || platform=="procurve_acl")
    {
        data.registerOption(m_dialog->iosacl_add_mirror_rule,
                            ropt, "iosacl_add_mirror_rule");
    }

    if (platform=="pix" || platform=="fwsm")
    {
        string vers = "version_" + version;
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
        if (Resources::platform_res[platform.toAscii().constData()]->getResourceBool(
              "/FWBuilderResources/Target/options/" +
              vers + "/pix_rule_syslog_settings"))
#else
        if (Resources::platform_res[platform.toLatin1().constData()]->getResourceBool(
              "/FWBuilderResources/Target/options/" +
              vers + "/pix_rule_syslog_settings"))
#endif
        {
            m_dialog->pix_disable_rule_log->setEnabled(true);
            m_dialog->pix_logLevel->setEnabled(true);
            m_dialog->pix_log_interval->setEnabled(true);

            data.registerOption(m_dialog->pix_disable_rule_log, ropt,
                                "disable_logging_for_this_rule");
            data.registerOption(m_dialog->pix_logLevel, ropt,
                                "log_level",logLevels);
            data.registerOption(m_dialog->pix_log_interval, ropt,
                                "log_interval");
        } else
        {
            m_dialog->pix_disable_rule_log->setEnabled(false);
            m_dialog->pix_logLevel->setEnabled(false);
            m_dialog->pix_log_interval->setEnabled(false);
        }

    }

    if (platform=="junosacl")
    {
        data.registerOption(m_dialog->counterLineEdit, ropt, "counter_name");
    }



    init = true;
    data.loadAll();

    m_dialog->pf_max_src_nodes->setEnabled(
        m_dialog->pf_source_tracking->isChecked());
    m_dialog->pf_max_src_states->setEnabled(
        m_dialog->pf_source_tracking->isChecked());


    connlimitAboveLabelChange();
    limitLabelChange();

    //apply->setEnabled(false);
    init=false;
}
예제 #7
0
void FirewallDialog::applyChanges()
{
    if (fwbdebug)
        qDebug() << "FirewallDialog::applyChanges()";

    bool autorename_chidren = false;
    QString dialog_txt = tr(
        "The name of the object '%1' has changed. The program can also "
        "rename IP address objects that belong to this object, "
        "using standard naming scheme 'host_name:interface_name:ip'. "
        "This makes it easier to distinguish what host or a firewall "
        "given IP address object belongs to when it is used in "
        "the policy or NAT rule. The program also renames MAC address "
        "objects using scheme 'host_name:interface_name:mac'. "
        "Do you want to rename child IP and MAC address objects now? "
        "(If you click 'No', names of all address objects that belong to "
        "%2 will stay the same.)")
        .arg(QString::fromUtf8(obj->getName().c_str()))
        .arg(QString::fromUtf8(obj->getName().c_str()));

    if (obj->getName() != m_dialog->obj_name->text().toUtf8().constData())
    {
        /*
         * when we open this warning dialog, FirewallDialog class
         * loses focus and obj_name lineEdit widget sends signal
         * "editingfinished" again.  To the user this looks like the
         * warning dialog popped up twice (in fact two copies of the
         * same warning dialog appear at the same time, one exactly on
         * top of another). To avoid this, block signals for the
         * duration while we show the dialog. Note that documentation
         * does not mention that QObject::blockSignals() affects not
         * only the widget but all its children, but it seems to work
         * that way. Tested with Qt 4.6.1. See #1171
         */
        blockSignals(true);
        autorename_chidren = (QMessageBox::warning(
                                  this,"Firewall Builder", dialog_txt,
                                  tr("&Yes"), tr("&No"), QString::null,
                                  0, 1 )==0 );
        blockSignals(false);
    }

    if (fwbdebug)
        qDebug() << "Sending FWCmdChange  autorename_chidren="
                 << autorename_chidren;

    std::unique_ptr<FWCmdChange> cmd(
        new FWCmdChange(m_project, obj, "", autorename_chidren));

    // new_state  is a copy of the fw object
    FWObject* new_state = cmd->getNewState();

    Firewall *s = dynamic_cast<Firewall*>(new_state);

#ifndef NDEBUG
    Management *mgmt = s->getManagementObject();
    assert(mgmt!=nullptr);
#endif

    string old_name = obj->getName();
    string new_name = string(m_dialog->obj_name->text().toUtf8().constData());
    string old_platform = obj->getStr("platform");
    string old_host_os = obj->getStr("host_OS");
    string old_version = obj->getStr("version");

    new_state->setName(new_name);
    m_dialog->commentKeywords->applyChanges(new_state);

    s->setInactive(m_dialog->inactive->isChecked());

    saveVersion(new_state);

    string new_version = new_state->getStr("version");

    string new_platform = readPlatform(m_dialog->platform).toLatin1().constData();
    if (new_platform.empty()) new_platform = "unknown";
    new_state->setStr("platform", new_platform );

    if (old_platform!=new_platform)
    {
        if (fwbdebug)
            qDebug() << "FirewallDialog::applyChanges() platform has changed"
                     << old_platform.c_str() << "->" << new_platform.c_str()
                     << "clearing option 'compiler'";
        platformChanged();
        FWOptions  *opt =s->getOptionsObject();
        opt->setStr("compiler", "");

        // Set default options for the new platform
        Resources::setDefaultTargetOptions(new_platform, s);
    }

    string new_host_os = readHostOS(m_dialog->hostOS).toLatin1().constData();
    if (new_host_os.empty()) new_host_os = "unknown_os";
    new_state->setStr("host_OS", new_host_os);

    if (old_host_os!=new_host_os)
    {
        if (fwbdebug)
            qDebug() << "FirewallDialog::applyChanges() host_OS has changed"
                     << old_host_os.c_str() << "->" << new_host_os.c_str();
        hostOSChanged();
        // Set default options for the new host os
        Resources::setDefaultTargetOptions(new_host_os, s);
    }

    if (new_platform.empty())
    {
        QMessageBox::critical(
            this, "Firewall Builder",
            tr("Platform setting can not be empty"),
            tr("&Continue"), nullptr, nullptr,
            0 );
        return;
    }

    if (new_host_os.empty())
    {
        QMessageBox::critical(
            this, "Firewall Builder",
            tr("Host OS setting can not be empty"),
            tr("&Continue"), nullptr, nullptr,
            0 );
        return;
    }

    if (old_platform!=new_platform || old_host_os!=new_host_os ||
        old_name!=new_name || old_version!=new_version)
    {
        if (fwbdebug)
            qDebug("FirewallDialog::applyChanges() scheduling call "
                   "to reopenFirewall()");
        m_project->registerRuleSetRedrawRequest();
    }

    if (!cmd->getOldState()->cmp(new_state, true))
    {
        if (obj->isReadOnly()) return;
        m_project->undoStack->push(cmd.release());
    }

    updateTimeStamps();
}