• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yast / yast-sudo / 6185557304

14 Sep 2023 12:40PM UTC coverage: 6.074%. First build
6185557304

push

github

web-flow
Merge pull request #29 from yast/backport_sp6

Backport sp6

1 of 1 new or added line in 1 file covered. (100.0%)

140 of 2305 relevant lines covered (6.07%)

0.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.63
/src/modules/Sudo.rb
1
# encoding: utf-8
2

3
# ------------------------------------------------------------------------------
4
# Copyright (c) 2006 Novell, Inc. All Rights Reserved.
5
#
6
#
7
# This program is free software; you can redistribute it and/or modify it under
8
# the terms of version 2 of the GNU General Public License as published by the
9
# Free Software Foundation.
10
#
11
# This program is distributed in the hope that it will be useful, but WITHOUT
12
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License along with
16
# this program; if not, contact Novell, Inc.
17
#
18
# To contact Novell about this file by physical or electronic mail, you may find
19
# current contact information at www.novell.com.
20
# ------------------------------------------------------------------------------
21

22
# File:        modules/Sudo.ycp
23
# Package:        Configuration of sudo
24
# Summary:        Sudo settings, input and output functions
25
# Authors:        Bubli <kmachalkova@suse.cz>
26
#
27
# $Id: Sudo.ycp 27914 2006-02-13 14:32:08Z locilka $
28
#
29
# Representation of the configuration of sudo.
30
# Input and output routines.
31
require "yast"
1✔
32

33
module Yast
1✔
34

35
  class UnsupportedSudoConfig < RuntimeError
1✔
36
    attr_reader :line
1✔
37

38
    def initialize(msg, line)
1✔
39
      super(msg)
3✔
40

41
      @line = line
3✔
42
    end
43
  end
44

45
  class SudoClass < Module
1✔
46
    def main
1✔
47
      Yast.import "UI"
12✔
48
      textdomain "sudo"
12✔
49

50
      Yast.import "Confirm"
12✔
51
      Yast.import "Map"
12✔
52
      Yast.import "Message"
12✔
53
      Yast.import "Progress"
12✔
54
      Yast.import "Report"
12✔
55
      Yast.import "Popup"
12✔
56
      Yast.import "Service"
12✔
57
      Yast.import "String"
12✔
58
      Yast.import "Summary"
12✔
59
      Yast.import "UsersPasswd"
12✔
60

61
      # Data was modified?
62
      @modified = false
12✔
63

64
      @sl = 10
12✔
65

66
      @ValidCharsUsername = Ops.add(
12✔
67
        Builtins.deletechars(String.CGraph, "'\""),
68
        " "
69
      )
70

71
      @settings2 = []
12✔
72
      @host_aliases2 = []
12✔
73
      @user_aliases2 = []
12✔
74
      @cmnd_aliases2 = []
12✔
75
      @runas_aliases2 = []
12✔
76
      @defaults = []
12✔
77
      @rules = []
12✔
78
      @all_users = []
12✔
79

80
      @citem = -1
12✔
81
    end
82

83
    # Data was modified?
84
    # @return true if modified
85
    def GetModified
1✔
86
      @modified
×
87
    end
88

89
    def SetModified
1✔
90
      @modified = true
×
91

92
      nil
93
    end
94

95
    def ReadSudoSettings2
1✔
96
      @settings2 = Convert.convert(
11✔
97
        SCR.Read(path(".sudo")),
98
        :from => "any",
99
        :to   => "list <list>"
100
      )
101
      Builtins.y2milestone("Sudo settings %1", @settings2)
11✔
102

103
      Builtins.foreach(@settings2) do |line|
11✔
104
        type = Ops.get_string(line, 1, "")
16✔
105
        case type
16✔
106
          when "Host_Alias"
107
            lst = Builtins.splitstring(
2✔
108
              Builtins.deletechars(Ops.get_string(line, 3, ""), " \t"),
109
              ","
110
            )
111
            @host_aliases2 = Builtins.add(
2✔
112
              @host_aliases2,
113
              {
114
                "c"    => Ops.get_string(line, 0, ""),
115
                "name" => Ops.get_string(line, 2, ""),
116
                "mem"  => lst
117
              }
118
            )
119
          when "User_Alias"
120
            lst = Builtins.splitstring(
2✔
121
              Builtins.deletechars(Ops.get_string(line, 3, ""), " \t"),
122
              ","
123
            )
124
            @user_aliases2 = Builtins.add(
2✔
125
              @user_aliases2,
126
              {
127
                "c"    => Ops.get_string(line, 0, ""),
128
                "name" => Ops.get_string(line, 2, ""),
129
                "mem"  => lst
130
              }
131
            )
132
          when "Cmnd_Alias", "Cmd_Alias"
133
            lst = Builtins.maplist(
4✔
134
              Builtins.splitstring(Ops.get_string(line, 3, ""), ",")
135
            ) do |s|
136
              Builtins.substring(s, Builtins.findfirstnotof(s, " \t"))
7✔
137
            end
138
            @cmnd_aliases2 = Builtins.add(
4✔
139
              @cmnd_aliases2,
140
              {
141
                "c"    => Ops.get_string(line, 0, ""),
142
                "name" => Ops.get_string(line, 2, ""),
143
                "mem"  => lst
144
              }
145
            )
146
          when "Runas_Alias"
147
            lst = Builtins.splitstring(
2✔
148
              Builtins.deletechars(Ops.get_string(line, 3, ""), " \t"),
149
              ","
150
            )
151
            @runas_aliases2 = Builtins.add(
2✔
152
              @runas_aliases2,
153
              {
154
                "c"    => Ops.get_string(line, 0, ""),
155
                "name" => Ops.get_string(line, 2, ""),
156
                "mem"  => lst
157
              }
158
            )
159
          else
160
            if Builtins.regexpmatch(type, "^Defaults.*$")
6✔
161
              #do nothing, keep defaults untouched
162
              @defaults = Builtins.add(@defaults, line)
×
163
            elsif type =~ /^sha\d+:/
6✔
164
              raise UnsupportedSudoConfig.new(
1✔
165
                _("Rules with digest are not supported."),
166
                "#{type} #{Ops.get_string(line, 2, "")} #{Ops.get_string(line, 3, "")}"
167
              )
168
            else
169
              m = {}
5✔
170
              cmd = []
5✔
171

172
              if Builtins.regexpmatch(type, "^.*\\\\.*$")
5✔
173
                type = Builtins.regexpsub(type, "^(.*)\\\\(.*)$", "\\1\\2")
×
174
              end
175
              Ops.set(m, "user", type)
5✔
176

177
              Ops.set(m, "host", Ops.get_string(line, 2, ""))
5✔
178

179
              #match "(.*)"
180
              if Builtins.regexpmatch(Ops.get_string(line, 3, ""), "\\(.*\\)")
5✔
181
                Ops.set(
1✔
182
                  m,
183
                  "run_as",
184
                  Builtins.regexpsub(
185
                    Ops.get_string(line, 3, ""),
186
                    "(\\(.*\\))",
187
                    "\\1"
188
                  )
189
                )
190
              end
191
              if Builtins.regexpmatch(
5✔
192
                  Ops.get_string(line, 3, ""),
193
                  "NOPASSWD:|SETENV:|NOEXEC:"
194
                )
195
                rest = Ops.get_string(line, 3, "")
4✔
196
                # remove from rest runas as it can also contain ":"
197
                if rest.gsub(/\([^\)]*\)/, "").count(":") > 1
4✔
198
                  raise UnsupportedSudoConfig.new(
2✔
199
                    _("Multiple tags on single line are not supported."),
200
                    "#{type} #{m["host"]} = #{Ops.get_string(line, 3, "")}"
201
                  )
202
                end
203
                Ops.set(
2✔
204
                  m,
205
                  "tag",
206
                  Builtins.regexpsub(
207
                    Ops.get_string(line, 3, ""),
208
                    "(NOPASSWD:|SETENV:|NOEXEC:)",
209
                    "\\1"
210
                  )
211
                )
212
              end
213

214
              # if(issubstring(line[3]:"","NOPASSWD:")) {
215
              #         m["no_passwd"] = (boolean) true;
216
              # }
217
              # else {
218
              #      m["no_passwd"] = (boolean) false;
219
              # }
220

221
              cmd = Builtins.splitstring(Ops.get_string(line, 3, ""), "):")
3✔
222
              Ops.set(
3✔
223
                m,
224
                "commands",
225
                Builtins.splitstring(
226
                  Ops.get(cmd, Ops.subtract(Builtins.size(cmd), 1), ""),
227
                  ","
228
                )
229
              )
230
              Ops.set(
3✔
231
                m,
232
                "commands",
233
                Builtins.maplist(Ops.get_list(m, "commands", [])) do |s|
234
                  Builtins.substring(s, Builtins.findfirstnotof(s, " \t"))
4✔
235
                end
236
              )
237
              Ops.set(m, "comment", Ops.get_string(line, 0, ""))
3✔
238

239
              @rules = Builtins.add(@rules, m)
3✔
240
            end
241
        end
242
      end
243

244
      Builtins.y2milestone(
8✔
245
        "user %1 host %2 runas %3 cmnd %4 def %5 rules %6",
246
        @user_aliases2,
247
        @host_aliases2,
248
        @runas_aliases2,
249
        @cmnd_aliases2,
250
        @defaults,
251
        @rules
252
      )
253

254

255
      true
8✔
256
    end
257

258

259
    def ReadLocalUsers
1✔
260
      return false if !UsersPasswd.Read({})
×
261

262
      users = Convert.convert(
×
263
        Builtins.merge(
264
          Map.Keys(UsersPasswd.GetUsers("local")),
265
          Map.Keys(UsersPasswd.GetUsers("system"))
266
        ),
267
        :from => "list",
268
        :to   => "list <string>"
269
      )
270

271
      available_groups = Convert.convert(
×
272
        Builtins.merge(
273
          Map.Keys(UsersPasswd.GetGroups("local")),
274
          Map.Keys(UsersPasswd.GetGroups("system"))
275
        ),
276
        :from => "list",
277
        :to   => "list <string>"
278
      )
279

280
      available_groups = Builtins.maplist(available_groups) do |group|
×
281
        Ops.add("%", group)
×
282
      end
283

284
      @all_users = Convert.convert(
×
285
        Builtins.merge(users, available_groups),
286
        :from => "list",
287
        :to   => "list <string>"
288
      )
289
      Builtins.y2milestone(
×
290
        "Read the following users and groups: %1",
291
        @all_users
292
      )
293

294
      true
×
295
    end
296

297
    def WriteSudoSettings2
1✔
298
      set = []
×
299

300
      Builtins.foreach(@host_aliases2) do |a|
×
301
        line = [
×
302
          Ops.get_string(a, "c", ""),
303
          "Host_Alias",
304
          Ops.get_string(a, "name", ""),
305
          Builtins.mergestring(Ops.get_list(a, "mem", []), ",")
306
        ]
307
        set = Builtins.add(set, line)
×
308
      end
309
      Builtins.foreach(@user_aliases2) do |a|
×
310
        line = [
×
311
          Ops.get_string(a, "c", ""),
312
          "User_Alias",
313
          Ops.get_string(a, "name", ""),
314
          Builtins.mergestring(Ops.get_list(a, "mem", []), ",")
315
        ]
316
        set = Builtins.add(set, line)
×
317
      end
318
      Builtins.foreach(@cmnd_aliases2) do |a|
×
319
        line = [
×
320
          Ops.get_string(a, "c", ""),
321
          "Cmnd_Alias",
322
          Ops.get_string(a, "name", ""),
323
          Builtins.mergestring(Ops.get_list(a, "mem", []), ",")
324
        ]
325
        set = Builtins.add(set, line)
×
326
      end
327

328
      set = Convert.convert(
×
329
        Builtins.merge(set, @defaults),
330
        :from => "list",
331
        :to   => "list <list>"
332
      )
333

334
      Builtins.foreach(@runas_aliases2) do |a|
×
335
        line = [
×
336
          Ops.get_string(a, "c", ""),
337
          "Runas_Alias",
338
          Ops.get_string(a, "name", ""),
339
          Builtins.mergestring(Ops.get_list(a, "mem", []), ",")
340
        ]
341
        set = Builtins.add(set, line)
×
342
      end
343

344
      Builtins.foreach(@rules) do |m|
×
345
        user = Ops.get_string(m, "user", "")
×
346
        user = Builtins.mergestring(Builtins.splitstring(user, "\\"), "\\\\")
×
347
        host = Ops.get_string(m, "host", "")
×
348
        comment = Ops.get_string(m, "comment", "")
×
349
        rest = Ops.add(
×
350
          Ops.add(
351
            Ops.add(Ops.get_string(m, "run_as", ""), " "),
352
            Ops.get_string(m, "tag", "")
353
          ),
354
          Builtins.mergestring(Ops.get_list(m, "commands", []), ",")
355
        )
356
        set = Builtins.add(set, [comment, user, host, rest])
×
357
      end
358

359
      Builtins.y2milestone("Sudo settings %1", set)
×
360

361
      SCR.Write(path(".sudo"), set) && SCR.Write(path(".sudo"), nil)
×
362
    end
363

364
    def SetItem(i)
1✔
365
      Builtins.y2internal("Current item %1", i)
×
366
      @citem = i
×
367

368
      nil
369
    end
370

371
    def GetItem
1✔
372
      @citem
×
373
    end
374

375
    def GetRules
1✔
376
      deep_copy(@rules)
2✔
377
    end
378

379
    def RemoveRule(i)
1✔
380
      @rules = Builtins.remove(@rules, i)
×
381

382
      nil
383
    end
384

385
    def GetRule(i)
1✔
386
      Ops.get(@rules, i, {})
×
387
    end
388

389
    def SetRule(i, spec)
1✔
390
      spec = deep_copy(spec)
×
391
      Ops.set(@rules, i, spec)
×
392

393
      nil
394
    end
395

396
    def SearchRules(what, key)
1✔
397
      found = false
×
398

399
      Builtins.foreach(@rules) do |m|
×
400
        if Builtins.haskey(m, what)
×
401
          if what == "commands"
×
402
            if Builtins.contains(Ops.get_list(m, what, []), key)
×
403
              found = true
×
404
              raise Break
×
405
            end
406
          else
407
            if Ops.get_string(m, what, "") == key
×
408
              found = true
×
409
              raise Break
×
410
            end
411
          end
412
        end
413
      end
414

415
      found
×
416
    end
417

418
    def SystemRulePopup(m, delete)
1✔
419
      m = deep_copy(m)
×
420
      if Ops.get_string(m, "user", "") == "ALL" &&
×
421
          Ops.get_string(m, "host", "") == "ALL" &&
422
          Ops.get_string(m, "run_as", "") == "(ALL)"
423
        text = _(
×
424
          "This rule is a system rule necessary for correct functionality of sudo.\n"
425
        )
426

427
        if delete
×
428
          text = Ops.add(
×
429
            text,
430
            _(
431
              "After deleting it, some applications may no longer work.\nReally delete?"
432
            )
433
          )
434
        else
435
          text = Ops.add(
×
436
            text,
437
            _(
438
              "If you change it, some applications may no longer work.\nReally edit? "
439
            )
440
          )
441
        end
442

443
        return Popup.YesNo(text)
×
444
      else
445
        return true
×
446
      end
447
    end
448

449

450
    def GetAliasNames(what)
1✔
451
      ret = []
×
452
      case what
×
453
        when "user"
454
          ret = Builtins.maplist(@user_aliases2) do |m|
×
455
            Ops.get_string(m, "name", "")
×
456
          end
457
        when "run_as"
458
          ret = Builtins.maplist(@runas_aliases2) do |m|
×
459
            Ops.get_string(m, "name", "")
×
460
          end
461
        when "host"
462
          ret = Builtins.maplist(@host_aliases2) do |m|
×
463
            Ops.get_string(m, "name", "")
×
464
          end
465
        when "command"
466
          ret = Builtins.maplist(@cmnd_aliases2) do |m|
×
467
            Ops.get_string(m, "name", "")
×
468
          end
469
        else
470
          return []
×
471
      end
472
      deep_copy(ret)
×
473
    end
474

475
    def SearchAlias2(name, aliases)
1✔
476
      aliases = deep_copy(aliases)
×
477
      tmp = []
×
478
      tmp = Builtins.filter(aliases) do |m|
×
479
        Ops.get_string(m, "name", "") == name
×
480
      end
481

482
      Ops.greater_than(Builtins.size(tmp), 0)
×
483
    end
484

485

486
    # Users
487
    def GetUserAliases2
1✔
488
      deep_copy(@user_aliases2)
1✔
489
    end
490

491
    def SetUserAlias(i, _alias)
1✔
492
      _alias = deep_copy(_alias)
×
493
      Ops.set(@user_aliases2, i, _alias)
×
494

495
      nil
496
    end
497

498
    def RemoveUserAlias2(i)
1✔
499
      @user_aliases2 = Builtins.remove(@user_aliases2, i)
×
500

501
      nil
502
    end
503

504
    # end Users
505

506
    # Hosts
507
    def GetHostAliases2
1✔
508
      deep_copy(@host_aliases2)
1✔
509
    end
510

511
    def RemoveHostAlias2(i)
1✔
512
      @host_aliases2 = Builtins.remove(@host_aliases2, i)
×
513

514
      nil
515
    end
516

517
    def SetHostAlias(i, _alias)
1✔
518
      _alias = deep_copy(_alias)
×
519
      Ops.set(@host_aliases2, i, _alias)
×
520

521
      nil
522
    end
523
    # end Hosts
524

525
    # RunAs
526
    def GetRunAsAliases2
1✔
527
      deep_copy(@runas_aliases2)
1✔
528
    end
529

530
    def RemoveRunAsAlias2(i)
1✔
531
      @runas_aliases2 = Builtins.remove(@runas_aliases2, i)
×
532

533
      nil
534
    end
535

536
    def SetRunAsAlias(i, _alias)
1✔
537
      _alias = deep_copy(_alias)
×
538
      Ops.set(@runas_aliases2, i, _alias)
×
539

540
      nil
541
    end
542

543
    # end RunAs
544

545
    # Commands
546
    def GetCmndAliases2
1✔
547
      deep_copy(@cmnd_aliases2)
3✔
548
    end
549

550
    def SetCmndAlias(i, _alias)
1✔
551
      _alias = deep_copy(_alias)
×
552
      Ops.set(@cmnd_aliases2, i, _alias)
×
553

554
      nil
555
    end
556

557
    def RemoveCmndAlias2(i)
1✔
558
      @cmnd_aliases2 = Builtins.remove(@cmnd_aliases2, i)
×
559

560
      nil
561
    end
562

563
    #end Commands
564

565
    def Abort
1✔
566
      if GetModified()
×
567
        return Popup.YesNo(
×
568
          _(
569
            "All changes will be lost. Really quit sudo configuration without saving?"
570
          )
571
        )
572
      else
573
        return true
×
574
      end
575
    end
576

577
    #  * Checks whether an Abort button has been pressed.
578
    #  * If so, calls function to confirm the abort call.
579
    #  :*
580
    #  * @return boolean true if abort confirmed
581
    def PollAbort
1✔
582
      return Abort() if UI.PollInput == :abort
×
583

584
      false
×
585
    end
586

587
    # Read all sudo settings
588
    # @return true on success
589
    def Read
1✔
590
      return false if !Confirm.MustBeRoot
×
591

592
      begin
×
593
        Report.Error(Message.CannotReadCurrentSettings) if !ReadSudoSettings2()
×
594
      rescue UnsupportedSudoConfig => e
595
        msg = _("Unsupported configuration found. YaST will now exit to prevent from breaking the system.")
×
596
        msg += "\n" + _("Issue: ") + e.message
×
597
        msg += "\n" + _("Line content: ") + e.line
×
598
        Report.Error(msg)
×
599

600
        return false
×
601
      end
602

603
      # Error message
604
      if !ReadLocalUsers()
×
605
        Report.Error(_("An error occurred while reading users and groups."))
×
606
      end
607

608
      # Sudo read dialog caption
609
      # string caption = _("Initializing sudo Configuration");
610
      #
611
      # integer steps = 2;
612
      #
613
      # Progress::New( caption, " ", steps, [
614
      #             /* Progress stage 1/2
615
      #             _("Read sudo settings"),
616
      #             /* Progress stage 2/2
617
      #             _("Read local users and groups")
618
      #         ], [
619
      #             /* Progress step 1/2
620
      #             _("Reading sudo settings..."),
621
      #             /* Progress step 2/2
622
      #             _("Reading local users and groups..."),
623
      #             /* Progress finished
624
      #             Message::Finished()
625
      #         ],
626
      #         ""
627
      # );
628

629
      @modified = false
×
630
      true
×
631
    end
632

633
    # Write all sudo settings
634
    # @return true on success
635
    def Write
1✔
636
      # Sudo read dialog caption
637
      caption = _("Saving sudo Configuration")
×
638

639
      steps = 1
×
640

641
      sl = 500
×
642

643
      ret = true
×
644

645
      # We do not set help text here, because it was set outside
646
      Progress.New(
×
647
        caption,
648
        " ",
649
        steps,
650
        [
651
          # Progress stage 1/1
652
          _("Write the settings")
653
        ],
654
        [
655
          # Progress step 1/1
656
          _("Writing the settings..."),
657
          # Progress finished
658
          Message.Finished
659
        ],
660
        ""
661
      )
662

663
      Builtins.sleep(sl)
×
664

665
      # write settings
666
      return false if PollAbort()
×
667
      Progress.NextStage
×
668
      # Error message
669
      if !WriteSudoSettings2()
×
670
        msg = _("Cannot write settings.")
×
671
        if ::File.exist?("/etc/sudoers.YaST2.new") # if file exists it is invalid syntax
×
672
          res = SCR.Execute(path(".target.bash_output"), "/usr/sbin/visudo -cf /etc/sudoers.YaST2.new")
×
673
          msg += _("\nSyntax error in target file. See /etc/sudoers.YaST2.new.\nDetails: ") + res["stdout"]
×
674
        end
675
        Report.Error(msg)
×
676
        ret = false
×
677
      end
678
      Builtins.sleep(sl)
×
679

680
      Progress.NextStage
×
681
      Builtins.sleep(sl)
×
682

683
      ret
×
684
    end
685

686
    publish :function => :GetModified, :type => "boolean ()"
1✔
687
    publish :function => :SetModified, :type => "void ()"
1✔
688
    publish :variable => :ValidCharsUsername, :type => "string"
1✔
689
    publish :variable => :all_users, :type => "list <string>"
1✔
690
    publish :function => :ReadLocalUsers, :type => "boolean ()"
1✔
691
    publish :function => :WriteSudoSettings2, :type => "boolean ()"
1✔
692
    publish :function => :SetItem, :type => "void (integer)"
1✔
693
    publish :function => :GetItem, :type => "integer ()"
1✔
694
    publish :function => :GetRules, :type => "list <map <string, any>> ()"
1✔
695
    publish :function => :RemoveRule, :type => "void (integer)"
1✔
696
    publish :function => :GetRule, :type => "map <string, any> (integer)"
1✔
697
    publish :function => :SetRule, :type => "void (integer, map <string, any>)"
1✔
698
    publish :function => :SearchRules, :type => "boolean (string, string)"
1✔
699
    publish :function => :SystemRulePopup, :type => "boolean (map <string, any>, boolean)"
1✔
700
    publish :function => :GetAliasNames, :type => "list <string> (string)"
1✔
701
    publish :function => :SearchAlias2, :type => "boolean (string, list <map <string, any>>)"
1✔
702
    publish :function => :GetUserAliases2, :type => "list <map <string, any>> ()"
1✔
703
    publish :function => :SetUserAlias, :type => "void (integer, map <string, any>)"
1✔
704
    publish :function => :RemoveUserAlias2, :type => "void (integer)"
1✔
705
    publish :function => :GetHostAliases2, :type => "list <map <string, any>> ()"
1✔
706
    publish :function => :RemoveHostAlias2, :type => "void (integer)"
1✔
707
    publish :function => :SetHostAlias, :type => "void (integer, map <string, any>)"
1✔
708
    publish :function => :GetRunAsAliases2, :type => "list <map <string, any>> ()"
1✔
709
    publish :function => :RemoveRunAsAlias2, :type => "void (integer)"
1✔
710
    publish :function => :SetRunAsAlias, :type => "void (integer, map <string, any>)"
1✔
711
    publish :function => :GetCmndAliases2, :type => "list <map <string, any>> ()"
1✔
712
    publish :function => :SetCmndAlias, :type => "void (integer, map <string, any>)"
1✔
713
    publish :function => :RemoveCmndAlias2, :type => "void (integer)"
1✔
714
    publish :function => :Abort, :type => "boolean ()"
1✔
715
    publish :function => :PollAbort, :type => "boolean ()"
1✔
716
    publish :function => :Read, :type => "boolean ()"
1✔
717
    publish :function => :Write, :type => "boolean ()"
1✔
718
  end
719

720
  Sudo = SudoClass.new
1✔
721
  Sudo.main
1✔
722
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc