r1218 + fix current VLAN calculation
[racktables] / gateways / switchvlans / main
1 #!/bin/sh
2
3 # This is a RackTables gateway for changing switch ports membership
4 # across VLANs. It works accordingly to the gateway protocol described
5 # in gateways.php and accepts the following commands on its stdin:
6 #
7 # * connect: connect to a switch, fetch all necessary data, store and
8 # disconnect
9 #
10 # * listvlans: list all VLANs found on the switch, propably filtering
11 # out those administratively prohibited. Only the VLANs from this
12 # list will be allowed as new destination for 'set' command.
13 #
14 # * listports: list all ports on the switch and their current status.
15 # Untagged (switchport mode access) ports will be shown with their
16 # VLAN ID and tagged ports will be shown as 'trunk' regardless of
17 # how many VLANs they are members of.
18 #
19 # * listmacs: output unsorted list of all dynamically learned MAC
20 # addresses present on the switch
21 #
22
23 endpoint=
24 hw=
25 sw=
26 user=
27 handler=
28 CONNECTED=0
29 MYDIR=`dirname $0`
30
31 authorized()
32 {
33 local endp=$1 user=$2 action=$3 arg1=$4 arg2=$5 skip=yes cval
34 [ -z "$endp" -o -z "$user" -o -z "$action" ] && return 1
35
36 # Now we strip PHP wrapping(s) and process auth rules only.
37 # Accept more than one ruleset on the floor.
38 while read line; do
39 if [ "$skip" = "yes" -a "$line" = "# S-T-A-R-T" ]; then
40 skip=no
41 continue
42 fi
43 if [ "$skip" = "no" -a "$line" = "# S-T-O-P" ]; then
44 skip=yes
45 continue
46 fi
47 [ "$skip" = "yes" ] && continue
48 # Allow comments.
49 [ -z "${line###*}" ] && continue
50
51 # Parse the line and try to make a decision earliest possible.
52 # Username and endpoint must match values/regexps, action
53 # must exactly match. Action arguments are tested agains values
54 # or regexps, but only for 'change' action.
55 # If the current rule doesn't match, advance to the next one.
56 # We will fail authorization by default anyway.
57
58 # Test action.
59 cval=`echo "$line" | cut -s -d' ' -f3`
60 [ "$action" = "$cval" ] || continue
61
62 # Test username.
63 cval=`echo "$line" | cut -s -d' ' -f2 | cut -s -d'@' -f1`
64 [ -z "${user##$cval}" ] || continue
65
66 # Test endpoint.
67 cval=`echo "$line" | cut -s -d' ' -f2 | cut -s -d'@' -f2`
68 [ -z "${endp##$cval}" ] || continue
69
70 if [ "$action" = "change" ]; then
71 [ -z "$arg1" -o -z "$arg2" ] && return 1
72 cval=`echo "$line" | cut -s -d' ' -f4`
73 [ -z "${arg1##$cval}" ] || continue
74 cval=`echo "$line" | cut -s -d' ' -f5`
75 [ -z "${arg2##$cval}" ] || continue
76 fi
77
78 # All criterias match. Pick the permission and bail out.
79 cval=`echo "$line" | cut -s -d' ' -f1`
80 if [ "$cval" = "allow" ]; then
81 return 0
82 else
83 return 1
84 fi
85 done < "$MYDIR/userauth.php"
86 return 1
87 }
88
89 # Not only connect, but gather all the data at once and remember the context.
90 do_connect()
91 {
92 endpoint=`echo $args | cut -s -d' ' -f1`
93 hw=`echo $args | cut -s -d' ' -f2`
94 sw=`echo $args | cut -s -d' ' -f3`
95 user=`echo $args | cut -s -d' ' -f4`
96 # sanity checks
97 if [ -z "$endpoint" -o -z "$hw" -o -z "$sw" -o -z "$user" ]; then
98 echo 'ERR!too few arguments to connect'
99 return
100 fi
101 case "$sw" in
102 Cisco+IOS+12.0|Cisco+IOS+12.2)
103 handler=cisco
104 ;;
105 *)
106 echo "ERR!Don't know how to handle $sw on $endpoint"
107 return
108 ;;
109 esac
110
111 # authorize user, look for "connect" privilege
112 if ! authorized $endpoint $user connect; then
113 echo "ERR!User $user is not authorized to connect to $endpoint"
114 return
115 fi
116
117 # prepare temp files
118 PORTINFO=`mktemp /tmp/racktables.XXXX`
119 if ! [ -f "$PORTINFO" ]; then
120 echo 'ERR!could not create portinfo tmpfile'
121 return
122 fi
123 VLANINFO=`mktemp /tmp/racktables.XXXX`
124 if ! [ -f "$VLANINFO" ]; then
125 echo 'ERR!could not create vlaninfo tmpfile'
126 rm -f "$PORTINFO"
127 return
128 fi
129 MACINFO=`mktemp /tmp/racktables.XXXX`
130 if ! [ -f "$MACINFO" ]; then
131 echo 'ERR!could not create MACinfo tmpfile'
132 rm -f "$PORTINFO" "$VLANINFO"
133 return
134 fi
135
136 # get the data
137 "$MYDIR/$handler.connector" $endpoint $hw $sw fetch "$VLANINFO" "$PORTINFO" "$MACINFO"
138 ret=$?
139 if [ $ret = 0 ]; then
140 CONNECTED=1
141 echo "OK!connected to $endpoint";
142 else
143 echo "ERR!Cannot connect to $endpoint, connector returned code $ret"
144 fi
145 }
146
147 do_listfile()
148 {
149 local F=$1
150 if ! [ -f "$F" ]; then
151 echo "ERR!Lost temp file '$F' on the way"
152 return
153 fi
154 echo -n 'OK!'
155 local semicolon=''
156 # tr might do the work, but use our chance to perform filtering once more
157 cat "$F" | while read line; do
158 [ "$line" = "" ] && continue
159 echo -n "$semicolon$line"
160 semicolon=';'
161 done
162 echo
163 }
164
165 do_set()
166 {
167 # sanity checks
168 local setline=$1
169 if [ -z "$setline" ]; then
170 echo 'ERR!missing set argument'
171 return
172 fi
173 local REQUESTS=`mktemp /tmp/racktables.XXXX`
174 local REPLIES=`mktemp /tmp/racktables.XXXX`
175 echo $1 | tr ';' '\n' | while read setexpr; do
176 portname=`echo $setexpr | cut -s -d'=' -f1`
177 newvlan=`echo $setexpr | cut -s -d'=' -f2`
178 curvlan=`egrep "^$portname=" $PORTINFO | cut -s -d'=' -f2 | cut -d',' -f2`
179 if [ -z "$curvlan" ]; then
180 echo "E!Could not find port $portname" >> "$REPLIES"
181 continue
182 fi
183 if [ "$curvlan" = "trunk" ]; then
184 echo "E!Port $portname is a trunk" >> "$REPLIES"
185 continue
186 fi
187 [ "$curvlan" = "$newvlan" ] && continue
188 # Authorize user for each change.
189 if ! authorized $endpoint $user change $curvlan $newvlan; then
190 echo "E!User $user is not authorized to assign port $portname@$endpoint from VLAN $curvlan to VLAN $newvlan" >> "$REPLIES"
191 continue
192 fi
193 echo "$portname $newvlan" >> "$REQUESTS"
194 done
195 nr=`egrep -c '^.' "$REPLIES"`
196 if [ "$nr" -ge 1 ]; then
197 echo "W!$nr change request(s) have been ignored" >> "$REPLIES"
198 fi
199
200 nq=`egrep -c '^.' "$REQUESTS"`
201 if [ "$nq" -ge 1 ]; then
202 # Go!
203 "$MYDIR/$handler.connector" $endpoint $hw $sw push "$REQUESTS" "$REPLIES" "$MACINFO"
204 local ret=$?
205
206 if [ $ret != 0 ]; then
207 echo "ERR!Failed to configure $endpoint, connector returned code $ret"
208 return
209 fi
210 echo "I!$nq change request(s) have been processed" >> "$REPLIES"
211 fi
212 echo -n 'OK!'
213 local SEMICOLON=
214 while read reply; do
215 echo -n $SEMICOLON$reply
216 SEMICOLON=';'
217 timestamp=`date '+%Y-%M-%d %H:%M:%S'`
218 [ -w "$MYDIR/changes.log" ] && echo "$timestamp $user@$endpoint $reply" >> "$MYDIR/changes.log"
219 done < "$REPLIES"
220 echo
221 rm -f "$REQUESTS" "$REPLIES"
222 }
223
224 # main loop
225 while read cmd args; do
226 case $cmd in
227 connect)
228 if [ $CONNECTED = 1 ]; then
229 echo 'ERR!Already connected'
230 else
231 do_connect $args
232 fi
233 ;;
234 listvlans)
235 if [ $CONNECTED = 1 ]; then
236 do_listfile "$VLANINFO"
237 else
238 echo 'ERR!Not connected'
239 fi
240 ;;
241 listports)
242 if [ $CONNECTED = 1 ]; then
243 do_listfile "$PORTINFO"
244 else
245 echo 'ERR!Not connected'
246 fi
247 ;;
248 listmacs)
249 if [ $CONNECTED = 1 ]; then
250 do_listfile "$MACINFO"
251 else
252 echo 'ERR!Not connected'
253 fi
254 ;;
255 set)
256 if [ $CONNECTED = 1 ]; then
257 do_set $args
258 else
259 echo 'ERR!Not connected'
260 fi
261 ;;
262 *)
263 echo "ERR!unknown command $cmd"
264 esac
265 done
266
267 rm -f "$PORTINFO" "$VLANINFO" "$MACINFO"
268 exit 0