etcutils 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ Njk0NDNmYzlkYjcwMGQwNjI4MWI5YTExNmYyMjg1MDJjN2IxY2IzYw==
5
+ data.tar.gz: !binary |-
6
+ NzQwMmFmMDNmYjE5N2NmYWMwOWU1MDBlYWQ4ZDFlMGIxYjcyOWI2Mg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ODg4YjI1MjQ2MDEzMzNkYWIzY2JiZDkxMGMyZTNiNjA0OGYzNzAxZDFjZDMx
10
+ ODgxODdjNDcxMjhiZjQ3NjFjNDMxMTI5M2ZiMjZiNjgyN2RlYTQ3YjcyZWNh
11
+ NDU2MzdhN2Q5MTVhY2VhM2I5ZmQ3NTVmZGU5ZjI2Y2ZhODljMWU=
12
+ data.tar.gz: !binary |-
13
+ YmM1MDMyZTVkZmU5YWZkY2M0MDMxODhhMTIwM2UwNGExNzEwZWEyZTQyYjNi
14
+ ZTYwZjJkZTRjNWE2MzUwY2U2NDgwNTRkYTY5ZWM5OWVjNmE4ZGE5MDhhOWUy
15
+ MWVlY2MyNTk1MTM1YTdmMjJkZGU2ZTVkNWMzYzg4MmU4NGMwMDc=
@@ -0,0 +1,22 @@
1
+ Makefile
2
+ *.*o
3
+ *.*bundle
4
+ *.log
5
+ *~
6
+ *.gem
7
+ *.rbc
8
+ .bundle
9
+ vendor/
10
+ .config
11
+ .yardoc
12
+ InstalledFiles
13
+ _yardoc
14
+ coverage
15
+ doc/
16
+ lib/bundler/man
17
+ pkg
18
+ rdoc
19
+ spec/reports
20
+ test/tmp
21
+ test/version_tmp
22
+ tmp
@@ -0,0 +1,19 @@
1
+ ---
2
+ language: ruby
3
+ rvm:
4
+ - 1.8.7-p374
5
+ - 1.9.3-p547
6
+ - 2.0
7
+ - 2.1
8
+ - ruby-head
9
+ before_install:
10
+ - git clean -dfx
11
+ - "[[ $(sudo grep secure_path /etc/sudoers) ]] && export rvmsudo_secure_path=1 || export rvmsudo_secure_path=0"
12
+ script:
13
+ - bundle update rake
14
+ - bundle exec rake compile
15
+ - bundle exec rake test
16
+ - rvmsudo bundle exec rake test
17
+ matrix:
18
+ allow_failures:
19
+ - rvm: ruby-head
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,20 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ etcutils (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ rake (10.3.1)
10
+ rake-compiler (0.9.2)
11
+ rake
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ bundler
18
+ etcutils!
19
+ rake
20
+ rake-compiler
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 David Campbell
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,335 @@
1
+ EtcUtils
2
+ ======
3
+
4
+ Ruby C Extension for read and write access to the Linux user database.
5
+
6
+ gem install etcutils
7
+
8
+ This gem can have catastrophic effects on your system if used incorrectly. I eventually would like to get to the point that it does all the hard thinking for you when it comes to writing to file, but for now you'll need to write your own tmp file (like /etc/passwd-) before moving it into place. This has worked for years for ```useradd``` and ```adduser``` and you should try keep that same mentality.
9
+
10
+ ## Deprecation Warning
11
+
12
+ NOTE: In i386, #to_s was removed in 0.1.5.
13
+
14
+ In the transitional release 0.1.5, the Struct method #to_s should no longer be used to print UserDB style strings. I'm not printing a warning when it is used, however, since #inspect calls #to_s and warnings would get annoying quickly.
15
+
16
+ The #to_s method will be removed from EtcUtils in release 1.0.0 (see feature/classes), which is scheduled to be released in late May/early June.
17
+
18
+ Moving forward, please use #to_entry in-place of #to_s. See [parse](#parse) for more.
19
+
20
+ I apologize for the inconvience.
21
+
22
+
23
+ ## Know Issues
24
+
25
+ Verified on Ubuntu 12.04, nsswitch.conf is misconfigured due to a known [bug](http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=699089). You will be unable to manipulate /etc/gshadow until this line is added to /etc/nsswitch.conf.
26
+
27
+ gshadow: files
28
+
29
+ OS X / BSD has not been thoroughly tested and should only be used to read from the userdb.
30
+
31
+ ## Classes
32
+
33
+ ##### EtcUtils::Passwd
34
+
35
+ ###### Linux
36
+ ```ruby
37
+ require 'etcutils'
38
+ EU.find_pwd(1)
39
+ EU::Passwd.find('daemon')
40
+ => #<EtcUtils::Passwd:0x000000018d6f48 @name="daemon", @passwd="x", @uid=1, @gid=1, @gecos="daemon", @directory="/usr/sbin", @shell="/bin/sh">
41
+ ```
42
+
43
+ * `:name`: This is the user’s login name. It should not contain capital letters.
44
+
45
+ * `:passwd`: This is either the encrypted user password, an asterisk (*), or the letter aqxaq. (See `pwconv(8)` for an explanation of aqxaq.)
46
+
47
+ * `:uid`: The privileged root login account (superuser) has the user ID 0.
48
+
49
+ * `:gid`: This is the numeric primary group ID for this user. (Additional groups for the user are defined in the system group file; see `group(5)`).
50
+
51
+ * `:gecos`: This field (sometimes called the “comment field”) is optional and used only for informational purposes. Usually, it contains the full username. Some programs (for example, `finger(1)`) display information from this field. GECOS stands for “General Electric Comprehensive Operating System”, which was renamed to GCOS when GE’s large systems division was sold to Honeywell. Dennis Ritchie has reported: “Sometimes we sent printer output or batch jobs to the GCOS machine. The gcos field in the password file was a place to stash the information for the $IDENTcard. Not elegant.”
52
+
53
+ * `:dir`: This is the user’s home directory: the initial directory where the user is placed after logging in. The value in this field is used to set the HOME environment variable.
54
+
55
+ * `:shell`: This is the program to run at login (if empty, use /bin/sh). If set to a nonexistent executable, the user will be unable to login through `login(1)`. The value in this field is used to set the SHELL environment variable.
56
+
57
+ ###### OS X
58
+ ```ruby
59
+ EtcUtils.find_pwd(1 || 'daemon')
60
+ EtcUtils::Passwd.find('daemon' || 1)
61
+ => #<EtcUtils::Passwd:0x007ff3919fb730 @name="daemon", @passwd="*", @uid=1, @gid=1, @gecos="System Services", @directory="/var/root", @shell="/usr/bin/false", @last_pw_change=0, @expire=0, @access_class="">
62
+ ```
63
+
64
+ * `:last_pw_change`: The date of the last password change, expressed as the number of days since Jan 1, 1970. The value 0 has a special meaning, which is that the user should change her pasword the next time she will log in the system. An empty field means that password aging features are disabled.
65
+
66
+ * `:expire`: The date of expiration of the account, expressed as the number of days since Jan 1, 1970. Note that an account expiration differs from a password expiration. In case of an acount expiration, the user shall not be allowed to login. In case of a password expiration, the user is not allowed to login using her password. An empty field means that the account will never expire. The value 0 should not be used as it is interpreted as either an account with no expiration, or as an expiration on Jan 1, 1970.
67
+
68
+
69
+ ##### EtcUtils::Group
70
+
71
+ ```ruby
72
+ EtcUtils.find_grp(1 || 'daemon')
73
+ EtcUtils::Group.find('daemon')
74
+ => #<EtcUtils::Group:0x007fda2c8a8d20 @name="daemon", @passwd="*", @gid=1, @members=["root"]>
75
+ ```
76
+
77
+ * `:name`: The name of the group.
78
+
79
+ * `:passwd`: The (encrypted) group password. If this field is empty, no password is needed.
80
+
81
+ * `:gid`: The numeric group ID.
82
+
83
+ * `:members`: A list of the usernames that are members of this group, separated by commas.
84
+
85
+ ##### EtcUtils::Shadow
86
+
87
+ The system must use shadow utils and the user must be able to read `SHADOW` or nil is returned.
88
+
89
+ ```ruby
90
+ SHADOW
91
+ => "/etc/shadow"
92
+ EtcUtils.find_spwd(1 || 'daemon')
93
+ EtcUtils::Shadow.find('daemon' || 1)
94
+ => #<struct EtcUtils::Shadow name="daemon", passwd="*", last_change=15729, min_change=0, max_change=99999, warn=7, inactive=-1, expire=-1, flag=-1>
95
+ ```
96
+
97
+ * `:name`: This is the user’s login name. It should not contain capital letters.
98
+
99
+ * `:passwd`: Refer to crypt(3) for details on how this string is interpreted. If the password field contains some string that is not a valid result of crypt(3), for instance ! or *, the user will not be able to use a unix password to log in (but the user may log in the system by other means). This field may be empty, in which case no passwords are required to authenticate as the specified login name. However, some applications which read the /etc/shadow file may decide not to permit any access at all if the password field is empty. A password field which starts with a exclamation mark means that the password is locked. The remaining characters on the line represent the password field before the password was locked.
100
+
101
+ * `:last_change`: The date of the last password change, expressed as the number of days since Jan 1, 1970. The value 0 has a special meaning, which is that the user should change her pasword the next time she will log in the system. An empty field means that password aging features are disabled.
102
+
103
+ * `:min_change`: The minimum password age is the number of days the user will have to wait before she will be allowed to change her password again. An empty field and value 0 mean that there are no minimum password age.
104
+
105
+ * `:max_change`: The maximum password age is the number of days after which the user will have to change her password. After this number of days is elapsed, the password may still be valid. The user should be asked to change her password the next time she will log in. An empty field means that there are no maximum password age, no password warning period, and no password inactivity period (see below). If the maximum password age is lower than the minimum password age, the user cannot change her password.
106
+
107
+ * `:warn`: The number of days before a password is going to expire (see the maximum password age above) during which the user should be warned. An empty field and value 0 mean that there are no password warning period.
108
+
109
+ * `:inactive`: The number of days after a password has expired (see the maximum password age above) during which the password should still be accepted (and the user should update her password during the next login). After expiration of the password and this expiration period is elapsed, no login is possible using the current user’s password. The user should contact her administrator. An empty field means that there are no enforcement of an inactivity period.
110
+
111
+ * `:expire`: The date of expiration of the account, expressed as the number of days since Jan 1, 1970. Note that an account expiration differs from a password expiration. In case of an acount expiration, the user shall not be allowed to login. In case of a password expiration, the user is not allowed to login using her password. An empty field means that the account will never expire. The value 0 should not be used as it is interpreted as either an account with no expiration, or as an expiration on Jan 1, 1970.
112
+
113
+ * `:flag`: This field is reserved for future use.
114
+
115
+
116
+ ##### EtcUtils::GShadow
117
+
118
+ If you're having trouble with GShadow, even as root, please see [known issues](#known-issues). Your nsswitch.conf file may contain a known bug.
119
+
120
+ The user must be able to read `GSHADOW` or nil is returned.
121
+
122
+ ```ruby
123
+ GSHADOW
124
+ => "/etc/gshadow"
125
+ EtcUtils.find_sgrp('daemon' || 1)
126
+ EtcUtils::Gshadow.find('daemon' || 1)
127
+ => #<struct EtcUtils::GShadow name="daemon", passwd="*", admins=[], members=[]>
128
+ ```
129
+
130
+ * `:name`: It must be a valid group name, which exist on the system.
131
+
132
+ * `:passwd`: Refer to `crypt(3)` for details on how this string is interpreted. If the password field contains some string that is not a valid result of `crypt(3)`, for instance ! or *, users will not be able to use a unix password to access the group (but group members do not need the password). The password is used when an user who is not a member of the group wants to gain the permissions of this group (see `newgrp(1)`). This field may be empty, in which case only the group members can gain the group permissions. A password field which starts with a exclamation mark means that the password is locked. The remaining characters on the line represent the password field before the password was locked. This password supersedes any password specified in _/etc/group_.
133
+
134
+ * `:admins`: It must be a comma-separated list of user names. Administrators can change the password or the members of the group. Administrators also have the same permissions as the members (see below).
135
+
136
+ * `:members`: It must be a comma-separated list of user names. Members can access the group without being prompted for a password. You should use the same list of users as in _/etc/group_.
137
+
138
+
139
+ # Usage
140
+
141
+ ## File locks
142
+
143
+ Passing a block to `EtcUtils.lock` is the preferred method of locking files, as unlock is always called regardless of exceptions
144
+
145
+ ```ruby
146
+ > begin
147
+ > lock {
148
+ > puts "INSIDE BLOCK: #{locked?}"
149
+ > raise "foobar"
150
+ > }
151
+ > rescue
152
+ > puts "RESCUED: #{locked?}"
153
+ > end
154
+ => INSIDE BLOCK: true
155
+ => RESCUED: false
156
+ ```
157
+
158
+ `:lckpwdf` `:lock`: Locking files will return true if locked. Calling lock on a locked file will return true. A return of false indicates lock failure.
159
+
160
+ `:ulckpwdf` `:unlock`: Unlocking will return true upon success. Subsequent calls against an unlocked file will return false.
161
+
162
+ To keep confusion at bay, calling `:locked?` will always return the true state of the lock.
163
+
164
+ ```ruby
165
+ EtcUtils.lckpwdf
166
+ => true
167
+ EtcUtils.lock
168
+ => true
169
+ EtcUtils.lock
170
+ => true
171
+ EtcUtils.locked?
172
+ => true
173
+ EtcUtils.ulckpwdf
174
+ => true
175
+ EtcUtils.ulckpwdf
176
+ => false
177
+ EtcUtils.unlock
178
+ => false
179
+ EtcUtils.locked?
180
+ => false
181
+ ```
182
+
183
+ ---
184
+
185
+ The below should apply to any of the above classes. If you find a bug, <b>please</b> open an issue.
186
+
187
+ XX can be replaced with
188
+
189
+ * pw # PASSWD
190
+ * gr # GROUP
191
+ * sp # SHADOW
192
+ * sg # GSHADOW
193
+
194
+ ## setXXent and getXXent
195
+
196
+ The `EtcUtils.setpwent` and `EtcUtils::Passwd.set` functions rewind to the beginning of the password database.
197
+
198
+ The `EtcUtils.endpwent` and `EtcUtils::Passwd.end` functions are used to close the password database after all processing has been performed.
199
+
200
+ Helper functions (literal 'XX') `EtcUtils.setXXent` and `EtcUtils.endXXent` rewind or close all database files.
201
+
202
+
203
+ ## Retrieving Entries
204
+
205
+ `#set` is not required on the initial attempt at retrieving entries.
206
+
207
+ ### getXXent
208
+
209
+ Calling `EtcUtils.getgrent` and `EtcUtils::Group.get` retrieves the first entry from the group database.
210
+
211
+ ```ruby
212
+ EtcUtils.getgrent
213
+ => #<class EtcUtils::Group name="root", passwd="x", gid=0, members=[]>
214
+ ...
215
+ EtcUtils::Group.get
216
+ => #<class EtcUtils::Group name="adm", passwd="x", gid=4, members=["ubuntu", "foobar"]>
217
+ EtcUtils.setgrent
218
+ => nil
219
+ EtcUtils::Group.get
220
+ => #<class EtcUtils::Group name="root", passwd="x", gid=0, members=[]>
221
+ ```
222
+
223
+ ### find
224
+
225
+ `EtcUtils::GShadow.find` can be called with either a string or an integer. In the case of shadow files, the corresponding etc file will be queried by uid/gid and returned.
226
+
227
+ ```ruby
228
+ EtcUtils::GShadow.find 'adm'
229
+ => #<class EtcUtils::Group name="adm", passwd="x", gid=4, members=["ubuntu", "foobar"]>
230
+ EtcUtils::GShadow.find 4
231
+ => #<class EtcUtils::Group name="adm", passwd="x", gid=4, members=["ubuntu", "foobar"]>
232
+ ```
233
+
234
+ ## New Entries
235
+
236
+ ### parse
237
+
238
+ If an entry already exists in the corresponding file, that object (un-altered) is returned.
239
+
240
+ ```ruby
241
+ p = EtcUtils::Passwd.find 1
242
+ => #<class EtcUtils::Passwd name="daemon", passwd="x", uid=1, gid=1, gecos="daemon", dir="/usr/sbin", shell="/bin/sh">
243
+ p.uid = 9999
244
+ => 9999
245
+ p.to_entry
246
+ => "daemon:x:9999:1:daemon:/usr/sbin:/bin/sh"
247
+ EtcUtils::Passwd.parse(p.to_entry)
248
+ => #<class EtcUtils::Passwd name="daemon", passwd="x", uid=1, gid=1, gecos="daemon", dir="/usr/sbin", shell="/bin/sh">
249
+ ```
250
+
251
+ If no entry is found, the new object is returned.
252
+
253
+ If uid/gid fields are left blank, the next available id is returned.
254
+
255
+ ```ruby
256
+ EtcUtils::Passwd.parse("foobar:x:::Foobar User:/home/foobar:/bin/shell")
257
+ => #<class EtcUtils::Passwd name="foobar", passwd="x", uid=11, gid=11, gecos="Foobar User", dir="/home/foobar", shell="/bin/shell">
258
+ ```
259
+ ### new
260
+
261
+ When called without args, an empty class is returned. When called with args, those args are used to populate the object.
262
+
263
+ ```ruby
264
+ EtcUtils::GShadow.new("foobar", '!', nil, ['sudo','adm'])
265
+ => #<class EtcUtils::GShadow name="foobar", passwd="!", admins=nil, members=["sudo", "adm"]>
266
+ EtcUtils::GShadow.new
267
+ => #<class EtcUtils::GShadow name=nil, passwd=nil, admins=nil, members=nil>
268
+ ```
269
+ ## Writing Entries
270
+
271
+ **Please be careful when you're writing to user database files.**
272
+
273
+ Before writing to any file, you should first create a backup. Conventional nomenclature is to append a dash to the end of the string. You'll also need you preserve file permissions. See `File.stat`.
274
+
275
+ ```ruby
276
+ GSHADOW + '-'
277
+ => "/etc/gshadow-"
278
+
279
+ stat = File.stat(GSHADOW).dup
280
+ File.open(GSHADOW + '-', 'w+', 0600) { |bf| bf.puts IO.readlines(GSHADOW) }
281
+
282
+ if stat.size != File.stat(GSHADOW + '-').size
283
+ raise_retry "#{GSHADOW} backup error"
284
+ end
285
+ ```
286
+
287
+ To update etc files, you should write all entries, including your updates, first to a temp file.
288
+
289
+ ```ruby
290
+ tmp = "/etc/_#{SHADOW.split('/').last}"
291
+ fh = File.open(SHADOW, 'r')
292
+
293
+ EtcUtils.lock {
294
+ File.open(tmp, File::RDWR|File::CREAT, 0600) { |tmp_fh|
295
+ while ( ent = EtcUtils::Shadow.get )
296
+ ent.fputs(tmp_fh)
297
+ end
298
+ }
299
+ }
300
+ fh.close
301
+ ```
302
+
303
+ ## Next UID/GID
304
+
305
+ `:next_uid` will store and increment the next available uid. Each time next_uid is called, that counter is incremented to the next available.
306
+
307
+ `:next_gid` will store and increment the next available gid. Each time next_gid is called, that counter is incremented to the next available.
308
+
309
+ ```ruby
310
+ EtcUtils.next_uid = 12
311
+ => 12
312
+ EtcUtils.next_uid
313
+ => 12
314
+ EtcUtils.next_uid
315
+ => 14
316
+ ```
317
+
318
+ `:parse` assigns `:next_uid` to `:next_gid` then confirmed as available. This attempts uid/gid in sync for new users.
319
+
320
+
321
+ ```ruby
322
+ EtcUtils.next_uid = 1000
323
+ => 1000
324
+ EtcUtils::Passwd.parse("foobar:x:::Foobar User:/home/foobar:/bin/shell")
325
+ => #<class EtcUtils::Passwd name="foobar", passwd="x", uid=1016, gid=1016, gecos="Foobar User", dir="/home/foobar", shell="/bin/shell">
326
+ ```
327
+
328
+ Although, when calling `:next_uid` or `:next_gid` they are not kept in sync. When creating new entries, it's recommended allow parse or new to manage uid/gids rather than assigning them yourself.
329
+
330
+ ```ruby
331
+ EtcUtils.next_uid = 19
332
+ => 19
333
+ EtcUtils.next_gid
334
+ => 1017
335
+ ```
@@ -0,0 +1,16 @@
1
+ require "rake"
2
+ require "rake/testtask"
3
+ require "bundler/gem_tasks"
4
+ require 'rake/extensiontask'
5
+
6
+ desc "Test EtcUtils"
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << "tests"
9
+ t.test_files = FileList['tests/**/test_*.rb']
10
+ end
11
+
12
+ Rake::ExtensionTask.new 'etcutils' do |ext|
13
+ ext.lib_dir = "lib/etcutils"
14
+ end
15
+
16
+ task :default => :test