CVE-2011-3177: Exposure of Sensitive Information to an Unauthorized Actor (CWE-200)
value: The YaST2 network created files with world readable permissions which could have allowed local users to read sensitive material out of network configuration files, like passwords for wireless networks.
CWE description: The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
CVSS v3 base score: 7.8
Published: 2017-09-08T18:29Z
Repository: https://github.com/yast/yast-core
Language: C++
Files changed: VERSION, agent-ini/doc/ag_ini.html, agent-ini/src/IniAgent.cc, agent-ini/src/IniFile.cc, agent-ini/src/IniFile.h, agent-ini/src/IniParser.cc, agent-ini/src/IniParser.h, agent-ini/testsuite/multi/private.err, agent-ini/testsuite/multi/private.in.1, agent-ini/testsuite/multi/private.in.2, agent-ini/testsuite/multi/private.out, agent-ini/testsuite/multi/private.scr, agent-ini/testsuite/multi/private.ycp, agent-system/src/SystemAgent.cc, agent-system/testsuite/tests/string.out, agent-system/testsuite/tests/string.ycp, package/yast2-core.changes
Commit date: 2012-01-05 17:03:40 +0000
Commit message: - ini-agent: added Write(.section_private.SECTION, BOOLEAN) (bnc#713661, CVE-2011-3177)
- system agent: added  Write(.target.string, [filename, mode],  content)

svn merge -r66758:66764 http://svn.opensuse.org/svn/yast/branches/SuSE-Code-11-SP2-Branch/core

svn path=/trunk/core/; revision=67117

Diff: diff --git a/VERSION b/VERSION
index f48f82fa..d93847fa 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.22.0
+2.22.1
diff --git a/agent-ini/doc/ag_ini.html b/agent-ini/doc/ag_ini.html
index dbc02b16..b710acf5 100644
--- a/agent-ini/doc/ag_ini.html
+++ b/agent-ini/doc/ag_ini.html
@@ -278,11 +278,11 @@ longer lines. Example:</p>
 </pre>
 
 <p>Then begin regexp is: ([^=]+)="([^"]*) and end regexp is
 ([^"]"). These are compared at the end so they are the last
 possibility. But once we get into this "divided line" by accident,
-it becomes greedy, so be carefull to forgotten ". If "multiline" is
+it becomes greedy, so be carefull to forgotten &quot;. If "multiline" is
 not present, this mechanism does not take in effect of course.</p>
 
 <p>See also the option <a
 href="#join_multiline">join_multiline</a>.</p>
 
@@ -471,11 +471,11 @@ equivalent IniAgent is:</p>
     $[
         "options" : [ "line_can_continue", "global_values", "join_multiline", "comments_last", "flat", ],
         "comments": [ "^[ \t]*#.*$", "#.*", "^[ \t]*$", ],
         "params" : [
             $[
-                "match" : [ "([a-zA-Z0-9_]+)[ \t]*=[ \t]*\"([^\"]*)\"", "%s=\"%s\"" ],
+                "match" : [ "([a-zA-Z0-9_]+)[ \t]*=[ \t]*\"([^\&quot;]*)\"", "%s=\"%s\"" ],
                 "multiline" : [ "([a-zA-Z0-9_]+)[ \t]*=[ \t]*\"([^\"]*)", "([^\"]*)\"", ],
             ],
             $[
                 "match" : [ "([a-zA-Z0-9_]+)[ \t]*=[ \t]*([^\"]*[^ \t\"]|)[ \t]*$", "%s=\"%s\"",],
             ],
@@ -531,10 +531,17 @@ was read by.</td>
 <tt>.ini.section_type.<i>sectionname</i>.<i>sectionname</i></tt></td>
 <td>identifies type of the section, which is the index of rule this
 section was read by.</td>
 </tr>
 
+<tr>
+<td>
+<tt>.ini.section_private.<i>sectionname</i></tt></td>
+<td>a boolean write-only property for sections corresponding to files.
+If true, the file will not be readable by group and others.</td>
+</tr>
+
 <tr class="notimpl">
 <td>
 <tt>.ini.section_file.<i>sectionname</i>.<i>sectionname</i></tt></td>
 <td>identifies type of the section, which is the index of <a
 href="#rewrite">rewrite</a> rule this section was read by.<br />
@@ -692,11 +699,11 @@ examples:<br />
         $[
         "begin" : [ "[ \t]*\\[Dialer[ \t]+(.*[^ \t])[ \t]*\\][ \t]*", "[Dialer %s]"        ],
       ],
       "params" : [
         $[
-        "match" : [ "^[ \t]*([^=]*[^ \t=])[ \t]*=[ \t]*(.*[^ \t]|)[ \t]*$" , "%s =       ],
+        "match" : [ "^[ \t]*([^=]*[^ \t=])[ \t]*=[ \t]*(.*[^ \t]|)[ \t]*$" , "%s = %s"   ],
     ],
     ]
   )
 )
 </pre>
diff --git a/agent-ini/src/IniAgent.cc b/agent-ini/src/IniAgent.cc
index a2883ffb..66d2c7e3 100644
--- a/agent-ini/src/IniAgent.cc
+++ b/agent-ini/src/IniAgent.cc
@@ -96,11 +96,11 @@ YCPBoolean IniAgent::Write(const YCPPath &path, const YCPValue& value, const YCP
 	ok = true;
     }
     else
     {
 	if (( parser.repeatNames () && value->isList ()) ||
-	    (!parser.repeatNames () &&  (value->isString () || value->isInteger())) ||
+	    (!parser.repeatNames () &&  (value->isString () || value->isBoolean() || value->isInteger())) ||
 	    path->component_str(0) == "all"
 	    )
 	    {
 		ok = true;
 		if (parser.inifile.Write (path, value, parser.HaveRewrites ()))
diff --git a/agent-ini/src/IniFile.cc b/agent-ini/src/IniFile.cc
index 7fe9fda2..23bbec66 100644
--- a/agent-ini/src/IniFile.cc
+++ b/agent-ini/src/IniFile.cc
@@ -73,10 +73,23 @@ YCPInteger as_integer (const YCPValue& v, const char * context)
     ycp2error ("Expected an integer for %s, got %s %s",
 	       context, v->valuetype_str(), v->toString().c_str());
     return YCPNull ();
 }
 
+/**
+ * Return the YCPBoolean or YCPNull if it is not one. Log an error.
+ */
+static
+YCPBoolean as_boolean (const YCPValue& v, const char * context)
+{
+    if (v->isBoolean ())
+	return v->asBoolean ();
+    ycp2error ("Expected a boolean for %s, got %s %s",
+	       context, v->valuetype_str(), v->toString().c_str());
+    return YCPNull ();
+}
+
 void IniSection::initValue (const string&key,const string&val,const string&comment,int rb)
 {
     string k = ip->changeCase (key);
     IniEntry e;
     IniEntryIdxIterator exi;
@@ -484,10 +497,13 @@ int IniSection::Write (const YCPPath&p, const YCPValue&v, bool rewrite)
       return setValue (p, v, 2, 1);
     if (s == "s" || s == "section" || s == "sc" || s == "section_comment" || s == "sectioncomment")
       return setSectionProp (p, v, 0, 1);
     if (s == "st" || s == "section_type" || s == "sectiontype")
       return setSectionProp (p, v, rewrite? 1:2, 1);
+    if (s == "section_private")
+      return setSectionProp (p, v, 3, 1);
+
     return -1;
 }
 
 int IniSection::setSectionProp (const YCPPath&p,const YCPValue&in, int what, int depth)
 {
@@ -589,16 +605,22 @@ int IniSection::setSectionProp (const YCPPath&p,const YCPValue&in, int what, int
 		    YCPInteger i = as_integer (prop, "section_rewrite");
 		    if (i.isNull())
 			return -1;
 		    s.setRewriteBy (i->value());
 		}
-		else {
+		else if (what == 2) {
 		    YCPInteger i = as_integer (prop, "section_type");
 		    if (i.isNull())
 			return -1;
 		    s.setReadBy (i->value());
 		}
+		else if (what == 3) {
+		    YCPBoolean b = as_boolean (prop, "section_private");
+		    if (b.isNull())
+			return -1;
+		    s.setPrivate (b->value());
+		}
 
 		if (xi != xe)
 		{
 		    ++xi;
 		}
diff --git a/agent-ini/src/IniFile.h b/agent-ini/src/IniFile.h
index 6a56c9ee..70c290d7 100644
--- a/agent-ini/src/IniFile.h
+++ b/agent-ini/src/IniFile.h
@@ -239,10 +239,17 @@ private:
      * if this is global section, there may be comment at the end
      * this is quite special case, it is impossible to change it
      */
     string end_comment;
 
+    /**
+     * It is effective only when the section corresponds to a file.
+     * The file will not be readable by group and others.
+     * bnc#713661
+     */
+    bool is_private;
+
     /** index to IniParser::rewrites for filename - section name mapping
      * It appears that read_by was used for both purposes,
      * causing bug (#19066).
      */
     int rewrite_by;
@@ -433,22 +440,22 @@ public:
     IniSection (const char *u): IniBase (u) {}
 
     IniSection (const IniParser *p)
 	: IniBase (-1),
 	  ip (p),
-	  end_comment (), rewrite_by(-1),
+	  end_comment (), is_private(false), rewrite_by(-1),
 	  container (), ivalues (), isections ()
 	    {}
 
     /**
      * Must define an own copy constructor
      * so that the indices point to the copy, not the original
      */
     IniSection (const IniSection &s) :
 	IniBase (s),
 	  ip (s.ip),
-	  end_comment (s.end_comment), rewrite_by (s.rewrite_by),
+          end_comment (s.end_comment), is_private(s.is_private), rewrite_by (s.rewrite_by),
 	  container (s.container)
 	{ reindex (); }
 
     void operator = (const IniSection &s)
 	{
@@ -456,11 +463,13 @@ public:
 	    {
 		return;
 	    } 
 	    IniBase::operator = (s);
 	    ip = s.ip;
-	    end_comment = s.end_comment; rewrite_by = s.rewrite_by;
+	    end_comment = s.end_comment;
+            is_private = s.is_private;
+            rewrite_by = s.rewrite_by;
 	    container = s.container;
 
 	    reindex ();
 	}
 
@@ -472,11 +481,11 @@ public:
      * @param n name of section
      */
     IniSection (const IniParser *p, string n)
 	: IniBase (n),
 	  ip (p),
-	  end_comment (), rewrite_by(0),
+	  end_comment (), is_private(false), rewrite_by(0),
 	  container(), ivalues (), isections ()
 	    {}
     /**
      * If value doesn't exist, creates new, otherwise calls method init of
      * the existing one.
@@ -509,10 +518,13 @@ public:
      * @param name name of a section
      * @return rewrite-by of section or -1 if the section wasn't found
      */
     int getSubSectionRewriteBy (const char*name);
 
+    void setPrivate(bool p) { is_private = p; }
+    bool isPrivate() const { return is_private; }
+
     /** 
      * If there is no comment at the beginning and no values and no
      * sections, it is better to set is as comment at the beginning.
      * Sets also dirty flag.
      * @param c comment
diff --git a/agent-ini/src/IniParser.cc b/agent-ini/src/IniParser.cc
index e68f091a..cdddea9d 100644
--- a/agent-ini/src/IniParser.cc
+++ b/agent-ini/src/IniParser.cc
@@ -963,23 +963,11 @@ int IniParser::write()
 			if (!s.isDirty ()) {
 			    y2debug ("Skipping file %s that was not changed.", filename.c_str());
 			    continue;
 			}
 			s.initReadBy ();
-			// ensure that the directories exist
-			Pathname pn (filename);
-			PathInfo::assert_dir (pn.dirname ());
-			ofstream of(filename.c_str());
-			if (!of.good())
-			{
-			    bugs++;
-			    y2error ("Can not open file %s for write", filename.c_str());
-			    continue;
-			}
-			write_helper (s, of, 0);
-			s.clean();
-			of.close ();
+                        bugs += write_file(filename, s);
 		    }
 		else
 		    {
 			y2error ("Value %s encountered at multifile top level",
 				 ci->e ().getName ());
@@ -995,28 +983,41 @@ int IniParser::write()
 		unlink ((*i).c_str());
 	    }
     }
     else
     {
-	// ensure that the directories exist
-	Pathname pn (file);
-	PathInfo::assert_dir (pn.dirname ());
-	ofstream of(file.c_str());
-	if (!of.good())
-	{
-	    y2error ("Can not open file %s for write", file.c_str());
-	    return -1;
-	}
-
-	write_helper (inifile, of, 0);
-
-	of.close();
+        bugs += write_file(file, inifile);
 	timestamp = getTimeStamp ();
     }
-    inifile.clean ();
     return bugs ? -1 : 0;
 }
+
+// return 0 on success, like write
+int IniParser::write_file(const string & filename, IniSection & section)
+{
+    // ensure that the directories exist
+    Pathname pn(filename);
+    PathInfo::assert_dir (pn.dirname ());
+
+    mode_t file_umask = section.isPrivate()? 0077: 0022;
+    mode_t orig_umask = umask(file_umask);
+    // rewriting an existing file wouldnt change its mode
+    unlink(filename.c_str());
+
+    ofstream of(filename.c_str());
+    if (!of.good()) {
+        y2error ("Can not open file %s for write", filename.c_str());
+        return -1;
+    }
+
+    write_helper (section, of, 0);
+
+    of.close();
+    umask(orig_umask);
+    return 0;
+}
+
 int IniParser::write_helper(IniSection&ini, ofstream&of, int depth)
 {
     char * out_buffer;
     string indent;
     string indent2;
diff --git a/agent-ini/src/IniParser.h b/agent-ini/src/IniParser.h
index ab954b64..22c9fec5 100644
--- a/agent-ini/src/IniParser.h
+++ b/agent-ini/src/IniParser.h
@@ -367,10 +367,14 @@ private:
 
     /**
      * Parse one ini file and build a structure of IniSection.
      */
     int parse_helper(IniSection&ini);
+    /**
+     * Write one ini file.
+     */
+    int write_file(const string & filename, IniSection & section);
     /**
      * Write one ini file.
      */
     int write_helper(IniSection&ini, ofstream&of,int depth);
 public:
diff --git a/agent-ini/testsuite/multi/private.err b/agent-ini/testsuite/multi/private.err
new file mode 100644
index 00000000..22a57fa3
--- /dev/null
+++ b/agent-ini/testsuite/multi/private.err
@@ -0,0 +1,11 @@
+[agent-ini] IniParser.cc(parse):XXX Rewriting multi/private.in.1.test to 1
+[agent-ini] IniParser.cc(parse):XXX Rewriting multi/private.in.2.test to 2
+[agent-ini] IniFile.cc(setMyValue):XXX Adding value .v."1"."Totalise"."Password" = "Secret password"
+[agent-ini] IniFile.cc(setMyValue):XXX Adding value .v."2"."arcor"."Password" = "Public password"
+[agent-ini] IniParser.cc(parse):XXX Rewriting multi/private.in.1.test to 1
+[agent-ini] IniParser.cc(parse):XXX Rewriting multi/private.in.2.test to 2
+[agent-ini] IniParser.cc(getFileName):XXX Rewriting 1 to multi/private.in.1.test
+[agent-ini] IniParser.cc(getFileName):XXX Rewriting 2 to multi/private.in.2.test
+[YCP] multi/private.ycp:XXX secret file mode: $["exit":0, "stderr":"", "stdout":"600\n"]
+[YCP] multi/private.ycp:XXX public file mode: $["exit":0, "stderr":"", "stdout":"644\n"]
+[agent-ini] IniParser.cc(write):XXX File multi/private.in.*.test did not change. Not saving.
diff --git a/agent-ini/testsuite/multi/private.in.1 b/agent-ini/testsuite/multi/private.in.1
new file mode 100644
index 00000000..6e0ae8d3
--- /dev/null
+++ b/agent-ini/testsuite/multi/private.in.1
@@ -0,0 +1,10 @@
+[Dialer Totalise]
+Compuserve = 0
+Provider = Totalise
+Phone = 08453001470
+[End]
+
+[Dialer force9]
+Provider = Force9
+Phone = 08451424000
+[End]
diff --git a/agent-ini/testsuite/multi/private.in.2 b/agent-ini/testsuite/multi/private.in.2
new file mode 100644
index 00000000..9b363770
--- /dev/null
+++ b/agent-ini/testsuite/multi/private.in.2
@@ -0,0 +1,11 @@
+[Dialer arcor]
+Compuserve = 0
+Provider = Arcor
+[End]
+
+[Dialer mobilcom]
+Compuserve = 0
+Provider = Mobilcom
+Phone = 0101901929
+[End]
+
diff --git a/agent-ini/testsuite/multi/private.out b/agent-ini/testsuite/multi/private.out
new file mode 100644
index 00000000..33d74784
--- /dev/null
+++ b/agent-ini/testsuite/multi/private.out
@@ -0,0 +1,25 @@
+(nil)
+multi/private.in.1.test  -------------------------------
+[Dialer Totalise]
+  Compuserve = 0
+  Provider = Totalise
+  Phone = 08453001470
+  Password = Secret password
+[End]
+
+[Dialer force9]
+  Provider = Force9
+  Phone = 08451424000
+[End]
+multi/private.in.2.test  -------------------------------
+[Dialer arcor]
+  Compuserve = 0
+  Provider = Arcor
+  Password = Public password
+[End]
+
+[Dialer mobilcom]
+  Compuserve = 0
+  Provider = Mobilcom
+  Phone = 0101901929
+[End]
diff --git a/agent-ini/testsuite/multi/private.scr b/agent-ini/testsuite/multi/private.scr
new file mode 100644
index 00000000..3c61b906
--- /dev/null
+++ b/agent-ini/testsuite/multi/private.scr
@@ -0,0 +1,24 @@
+.
+
+`ag_ini(
+  `IniAgent( [ "multi/private.in.*.test" ],
+    $[
+      "rewrite" : [
+	[ "multi/private\.in\.(.)\.test$", "multi/private.in.%s.test"],
+      ],
+      "subindent" : "  ",
+      "comments": [ "^[ \t]*#.*", "^[ \t]*$" ],
+      "sections" : [
+        $[
+        "begin" : [ "[ \t]*\\[Dialer[ \t]+(.*[^ \t])[ \t]*\\][ \t]*", "[Dialer %s]" ],
+	"end" : [ "^[ \t]*\\[End\\][ \t]*$", "[End]", ],
+        ],
+      ],
+      "params" : [
+        $[
+        "match" : [ "^[ \t]*([^=]*[^ \t=])[ \t]*=[ \t]*(.*[^ \t]|)[ \t]*$" , "%s = %s"],
+      ],
+    ],
+    ]
+  )
+)
diff --git a/agent-ini/testsuite/multi/private.ycp b/agent-ini/testsuite/multi/private.ycp
new file mode 100644
index 00000000..c7b2c77f
--- /dev/null
+++ b/agent-ini/testsuite/multi/private.ycp
@@ -0,0 +1,26 @@
+//
+// Multiple files test
+//
+// providers.rpm
+//
+
+
+{
+    SCR::RegisterAgent (.target, `ag_system ());
+    any out = nil;
+
+    SCR::Write (.v."1"."Totalise"."Password", "Secret password");
+    SCR::Write (.section_private."1", true);
+
+    SCR::Write (.v."2"."arcor"."Password", "Public password");
+
+    SCR::Write (., nil); //flush
+
+    out = SCR::Execute (.target.bash_output, "stat -c %a multi/private.in.1.test");
+    y2milestone("secret file mode: %1", out);
+
+    out = SCR::Execute (.target.bash_output, "stat -c %a multi/private.in.2.test");
+    y2milestone("public file mode: %1", out);
+
+    return nil;
+}
diff --git a/agent-system/src/SystemAgent.cc b/agent-system/src/SystemAgent.cc
index 4441f4d4..d34b2419 100644
--- a/agent-system/src/SystemAgent.cc
+++ b/agent-system/src/SystemAgent.cc
@@ -707,29 +707,54 @@ SystemAgent::Write (const YCPPath& path, const YCPValue& value,
 
     else if (cmd == "string")
     {
 	/**
 	 * @builtin Write (.target.string, string filename, string value) -> boolean
+	 * @builtin Write (.target.string, [string filename, integer filemode] , string value) -> boolean
 	 * Writes the string <tt>value</tt> into a file. If the file already
 	 * exists, the existing file is overwritten. The return value is
 	 * true, if the file has been written successfully.
+         *
+         * @example Write(.target.string, "/etc/papersize", "a4") -> true
+         * @example Write(.target.string, ["/etc/rsyncd.secrets", 0600], "user:passwd") -> true
 	 */
 
-	if (value.isNull() || !value->isString())
+	if (value.isNull() || !(value->isString() || value->isList()))
 	{
 	    ycp2error ("Bad filename arg for Write (.string ...)");
 	    return YCPBoolean (false);
 	}
 
+	string filename;
+	mode_t filemode = 0644;
+
+	if (value->isString())
+	{
+	    filename = value->asString()->value();
+	}
+	else
+	{			// value is list
+	    YCPList flist = value->asList();
+	    if ((flist->size() != 2)
+		|| (!flist->value(0)->isString())
+		|| (!flist->value(1)->isInteger()))
+	    {
+		ycp2error ("Bad [filename, mode] list in call to Write (%s, [ string filename, integer mode ], ...)",
+		    cmd.c_str ());
+		return YCPBoolean (false);
+	    }
+	    filename = flist->value(0)->asString()->value();
+	    filemode = (int)(flist->value(1)->asInteger()->value());
+	}
+
 	if (arg.isNull() || !arg->isString())
 	{
 	    ycp2error ("Bad string value for Write (.string ...)");
 	    return YCPBoolean (false);
 	}
 
-	string filename = value->asString()->value();
-	int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+	int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, filemode);
 	if (fd >= 0)
 	{
 	    string cont = arg->asString()->value();
 	    const char *buffer = cont.c_str();
 	    size_t length = cont.length();
@@ -1037,12 +1062,12 @@ SystemAgent::Execute (const YCPPath& path, const YCPValue& value,
 	 * @builtin Execute (.target.symlink, string oldpath, string newpath) -> boolean
 	 *
 	 * Creates a symbolic link named newpath which contains the
 	 * string oldpath.
 	 *
-	 * Symbolic links are interpreted at run-time as if the  con­
-	 * tents of the link had been substituted into the path being
+	 * Symbolic links are interpreted at run-time as if the contents
+	 * of the link had been substituted into the path being
 	 * followed to find a file or directory.
 	 *
 	 * The return value is true or false, depending of the success.
 	 *
 	 * @example Execute (.target.symlink, "/lib/YaST2", "Y2")
diff --git a/agent-system/testsuite/tests/string.out b/agent-system/testsuite/tests/string.out
index 00128e28..e76e1a80 100644
--- a/agent-system/testsuite/tests/string.out
+++ b/agent-system/testsuite/tests/string.out
@@ -1,5 +1,6 @@
 ("")
 ("Software is like sex. It's better when it's free.\n")
 (true)
 (nil)
 ("never mind")
+("secret file mode: 600\n")
diff --git a/agent-system/testsuite/tests/string.ycp b/agent-system/testsuite/tests/string.ycp
index 96893a15..2b71cf10 100644
--- a/agent-system/testsuite/tests/string.ycp
+++ b/agent-system/testsuite/tests/string.ycp
@@ -22,5 +22,15 @@
 
 {
     // this must not produce a error in the log
     return SCR::Read (.string, ["not-here.txt", "never mind"]);
 }
+
+{
+    map out = nil;
+    string filename = "tmp.secret.string";
+
+    SCR::Write (.string, [filename, 0600], "This is secret\n");
+
+    out = (map) SCR::Execute (.bash_output, "stat -c %a " + filename);
+    return "secret file mode: " + out["stdout"]:"";
+}
diff --git a/package/yast2-core.changes b/package/yast2-core.changes
index 33ac13c0..82a7b217 100644
--- a/package/yast2-core.changes
+++ b/package/yast2-core.changes
@@ -1,5 +1,13 @@
+-------------------------------------------------------------------
+Mon Nov  7 18:19:27 CET 2011 - mvidner@suse.cz
+
+- ini-agent: added Write(.section_private.SECTION, BOOLEAN)
+  (bnc#713661, CVE-2011-3177)
+- system agent: added  Write(.target.string, [filename, mode], content)
+- 2.22.1
+
 -------------------------------------------------------------------
 Fri Dec  2 11:49:56 UTC 2011 - lslezak@suse.cz
 
 - process-agent - fixed testuite (removed potential race condition)
 - 2.22.0

Vulnerable code: 2.22.0
it becomes greedy, so be carefull to forgotten ". If "multiline" is
                "match" : [ "([a-zA-Z0-9_]+)[ \t]*=[ \t]*\"([^\"]*)\"", "%s=\"%s\"" ],
        "match" : [ "^[ \t]*([^=]*[^ \t=])[ \t]*=[ \t]*(.*[^ \t]|)[ \t]*$" , "%s =       ],
	    (!parser.repeatNames () &&  (value->isString () || value->isInteger())) ||
		else {
	  end_comment (), rewrite_by(-1),
	  end_comment (s.end_comment), rewrite_by (s.rewrite_by),
	    end_comment = s.end_comment; rewrite_by = s.rewrite_by;
	  end_comment (), rewrite_by(0),
			// ensure that the directories exist
			Pathname pn (filename);
			PathInfo::assert_dir (pn.dirname ());
			ofstream of(filename.c_str());
			if (!of.good())
			{
			    bugs++;
			    y2error ("Can not open file %s for write", filename.c_str());
			    continue;
			}
			write_helper (s, of, 0);
			s.clean();
			of.close ();
	// ensure that the directories exist
	Pathname pn (file);
	PathInfo::assert_dir (pn.dirname ());
	ofstream of(file.c_str());
	if (!of.good())
	{
	    y2error ("Can not open file %s for write", file.c_str());
	    return -1;
	}

	write_helper (inifile, of, 0);

	of.close();
    inifile.clean ();
	if (value.isNull() || !value->isString())
	string filename = value->asString()->value();
	int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
	 * Symbolic links are interpreted at run-time as if the  con­
	 * tents of the link had been substituted into the path being
Fixed code: 2.22.1
it becomes greedy, so be carefull to forgotten &quot;. If "multiline" is
                "match" : [ "([a-zA-Z0-9_]+)[ \t]*=[ \t]*\"([^\&quot;]*)\"", "%s=\"%s\"" ],
<tr>
<td>
<tt>.ini.section_private.<i>sectionname</i></tt></td>
<td>a boolean write-only property for sections corresponding to files.
If true, the file will not be readable by group and others.</td>
</tr>

        "match" : [ "^[ \t]*([^=]*[^ \t=])[ \t]*=[ \t]*(.*[^ \t]|)[ \t]*$" , "%s = %s"   ],
	    (!parser.repeatNames () &&  (value->isString () || value->isBoolean() || value->isInteger())) ||
/**
 * Return the YCPBoolean or YCPNull if it is not one. Log an error.
 */
static
YCPBoolean as_boolean (const YCPValue& v, const char * context)
{
    if (v->isBoolean ())
	return v->asBoolean ();
    ycp2error ("Expected a boolean for %s, got %s %s",
	       context, v->valuetype_str(), v->toString().c_str());
    return YCPNull ();
}

    if (s == "section_private")
      return setSectionProp (p, v, 3, 1);

		else if (what == 2) {
		else if (what == 3) {
		    YCPBoolean b = as_boolean (prop, "section_private");
		    if (b.isNull())
			return -1;
		    s.setPrivate (b->value());
		}
    /**
     * It is effective only when the section corresponds to a file.
     * The file will not be readable by group and others.
     * bnc#713661
     */
    bool is_private;

	  end_comment (), is_private(false), rewrite_by(-1),
          end_comment (s.end_comment), is_private(s.is_private), rewrite_by (s.rewrite_by),
	    end_comment = s.end_comment;
            is_private = s.is_private;
            rewrite_by = s.rewrite_by;
	  end_comment (), is_private(false), rewrite_by(0),
    void setPrivate(bool p) { is_private = p; }
    bool isPrivate() const { return is_private; }

                        bugs += write_file(filename, s);
        bugs += write_file(file, inifile);

// return 0 on success, like write
int IniParser::write_file(const string & filename, IniSection & section)
{
    // ensure that the directories exist
    Pathname pn(filename);
    PathInfo::assert_dir (pn.dirname ());

    mode_t file_umask = section.isPrivate()? 0077: 0022;
    mode_t orig_umask = umask(file_umask);
    // rewriting an existing file wouldnt change its mode
    unlink(filename.c_str());

    ofstream of(filename.c_str());
    if (!of.good()) {
        y2error ("Can not open file %s for write", filename.c_str());
        return -1;
    }

    write_helper (section, of, 0);

    of.close();
    umask(orig_umask);
    return 0;
}

    /**
     * Write one ini file.
     */
    int write_file(const string & filename, IniSection & section);
[agent-ini] IniParser.cc(parse):XXX Rewriting multi/private.in.1.test to 1
[agent-ini] IniParser.cc(parse):XXX Rewriting multi/private.in.2.test to 2
[agent-ini] IniFile.cc(setMyValue):XXX Adding value .v."1"."Totalise"."Password" = "Secret password"
[agent-ini] IniFile.cc(setMyValue):XXX Adding value .v."2"."arcor"."Password" = "Public password"
[agent-ini] IniParser.cc(parse):XXX Rewriting multi/private.in.1.test to 1
[agent-ini] IniParser.cc(parse):XXX Rewriting multi/private.in.2.test to 2
[agent-ini] IniParser.cc(getFileName):XXX Rewriting 1 to multi/private.in.1.test
[agent-ini] IniParser.cc(getFileName):XXX Rewriting 2 to multi/private.in.2.test
[YCP] multi/private.ycp:XXX secret file mode: $["exit":0, "stderr":"", "stdout":"600\n"]
[YCP] multi/private.ycp:XXX public file mode: $["exit":0, "stderr":"", "stdout":"644\n"]
[agent-ini] IniParser.cc(write):XXX File multi/private.in.*.test did not change. Not saving.
[Dialer Totalise]
Compuserve = 0
Provider = Totalise
Phone = 08453001470
[End]

[Dialer force9]
Provider = Force9
Phone = 08451424000
[End]
[Dialer arcor]
Compuserve = 0
Provider = Arcor
[End]

[Dialer mobilcom]
Compuserve = 0
Provider = Mobilcom
Phone = 0101901929
[End]

(nil)
multi/private.in.1.test  -------------------------------
[Dialer Totalise]
  Compuserve = 0
  Provider = Totalise
  Phone = 08453001470
  Password = Secret password
[End]

[Dialer force9]
  Provider = Force9
  Phone = 08451424000
[End]
multi/private.in.2.test  -------------------------------
[Dialer arcor]
  Compuserve = 0
  Provider = Arcor
  Password = Public password
[End]

[Dialer mobilcom]
  Compuserve = 0
  Provider = Mobilcom
  Phone = 0101901929
[End]
.

`ag_ini(
  `IniAgent( [ "multi/private.in.*.test" ],
    $[
      "rewrite" : [
	[ "multi/private\.in\.(.)\.test$", "multi/private.in.%s.test"],
      ],
      "subindent" : "  ",
      "comments": [ "^[ \t]*#.*", "^[ \t]*$" ],
      "sections" : [
        $[
        "begin" : [ "[ \t]*\\[Dialer[ \t]+(.*[^ \t])[ \t]*\\][ \t]*", "[Dialer %s]" ],
	"end" : [ "^[ \t]*\\[End\\][ \t]*$", "[End]", ],
        ],
      ],
      "params" : [
        $[
        "match" : [ "^[ \t]*([^=]*[^ \t=])[ \t]*=[ \t]*(.*[^ \t]|)[ \t]*$" , "%s = %s"],
      ],
    ],
    ]
  )
)
//
// Multiple files test
//
// providers.rpm
//


{
    SCR::RegisterAgent (.target, `ag_system ());
    any out = nil;

    SCR::Write (.v."1"."Totalise"."Password", "Secret password");
    SCR::Write (.section_private."1", true);

    SCR::Write (.v."2"."arcor"."Password", "Public password");

    SCR::Write (., nil); //flush

    out = SCR::Execute (.target.bash_output, "stat -c %a multi/private.in.1.test");
    y2milestone("secret file mode: %1", out);

    out = SCR::Execute (.target.bash_output, "stat -c %a multi/private.in.2.test");
    y2milestone("public file mode: %1", out);

    return nil;
}
	 * @builtin Write (.target.string, [string filename, integer filemode] , string value) -> boolean
         *
         * @example Write(.target.string, "/etc/papersize", "a4") -> true
         * @example Write(.target.string, ["/etc/rsyncd.secrets", 0600], "user:passwd") -> true
	if (value.isNull() || !(value->isString() || value->isList()))
	string filename;
	mode_t filemode = 0644;

	if (value->isString())
	{
	    filename = value->asString()->value();
	}
	else
	{			// value is list
	    YCPList flist = value->asList();
	    if ((flist->size() != 2)
		|| (!flist->value(0)->isString())
		|| (!flist->value(1)->isInteger()))
	    {
		ycp2error ("Bad [filename, mode] list in call to Write (%s, [ string filename, integer mode ], ...)",
		    cmd.c_str ());
		return YCPBoolean (false);
	    }
	    filename = flist->value(0)->asString()->value();
	    filemode = (int)(flist->value(1)->asInteger()->value());
	}

	int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, filemode);
	 * Symbolic links are interpreted at run-time as if the contents
	 * of the link had been substituted into the path being
("secret file mode: 600\n")

{
    map out = nil;
    string filename = "tmp.secret.string";

    SCR::Write (.string, [filename, 0600], "This is secret\n");

    out = (map) SCR::Execute (.bash_output, "stat -c %a " + filename);
    return "secret file mode: " + out["stdout"]:"";
}
-------------------------------------------------------------------
Mon Nov  7 18:19:27 CET 2011 - mvidner@suse.cz

- ini-agent: added Write(.section_private.SECTION, BOOLEAN)
  (bnc#713661, CVE-2011-3177)
- system agent: added  Write(.target.string, [filename, mode], content)
- 2.22.1