1 |
|
2 |
|
3 |
File: libdata_install.txt |
4 |
Title: LibData Installation |
5 |
Author: Paul F. Bramscher brams006@umn.edu |
6 |
Date: November 21, 2003 |
7 |
|
8 |
|
9 |
============================================================================== |
10 |
TABLE OF CONTENTS |
11 |
============================================================================== |
12 |
|
13 |
1.0 Infrastructure |
14 |
2.0 Architecture |
15 |
3.0 Installation Procedures |
16 |
4.0 Troubleshooting |
17 |
|
18 |
============================================================================== |
19 |
1.0 INFRASTRUCTURE |
20 |
============================================================================== |
21 |
|
22 |
Hardware Developed and tested on generic intel-based Linux |
23 |
servers and Sun servers. Other hardware (Macintosh) |
24 |
might be possible with some code modifications. |
25 |
Queries can be complex and frequent to render pages. |
26 |
A 2 GHz+ server with 1 GB RAM is recommended for a modest |
27 |
installation in a production environment. |
28 |
Disk footprint for the PHP code itself is well under 5 MB. |
29 |
Additional disk requirements due to database size depends |
30 |
on your local utilization and data growth rate -- and |
31 |
should be monitored as it grows. However, LibData is |
32 |
strongly normalized and unlikely to ever present a disk |
33 |
space issue. |
34 |
|
35 |
Operating System Developed and tested on both Red Hat Linux 9 and Solaris |
36 |
unix. Other operating systems (BSD, Mac OS X, etc.) |
37 |
might be possible with varying degrees of modification. |
38 |
Extensive modification would likely be required to run |
39 |
LibData on a Windows-based computer with an Apache, PHP, |
40 |
and mySQL installation (NOT recommended). |
41 |
|
42 |
Web Server Apache. It is possible to run LibData on a server |
43 |
without a DNS name, although it must be a routable |
44 |
IP. It is strongly recommended to split LibData into |
45 |
two directories, one serving public interfaces, and the |
46 |
other in an SSL location (refer to installation steps |
47 |
below). |
48 |
|
49 |
Database mySQL. Note that LibData was initially developed on a |
50 |
version of mySQL without support for transactions, and this |
51 |
substantially affects the atomicity of the SQL code (lacking |
52 |
the rollback feature). Future versions of LibData will most |
53 |
likely be written with transactions in mind, and much more |
54 |
optimized SQL. |
55 |
|
56 |
Programming Language Written exclusively in PHP. Coding is structured/function |
57 |
based for simplicity, using PHP object encapsulation only for |
58 |
security (session-related) purposes. As this is version 1.0, |
59 |
without outside grants or special funding, code has certainly not |
60 |
been optimized. However, it is highly self-documented and |
61 |
meaningful variable names have generally been used throughout. |
62 |
|
63 |
Recommended Browsers Note that no client-side programming was done (other than HTML |
64 |
and modest usage of CSS). Therefore LibData ought to be quite |
65 |
widely viewable by most any browser available. Additionally, |
66 |
there are no frames to contend with. Viewing is best at 1024 x 768, |
67 |
particularly in the authoring/administrative environments, though |
68 |
800 x 600 is also supported. In keeping with the spirit of open |
69 |
source, we recommend Mozilla which renders HTML cleaner than |
70 |
MSIE (for example, no extra spaces when a FORM tag is within a |
71 |
table cell). LibData was developed with Mozilla specifically in mind. |
72 |
|
73 |
============================================================================== |
74 |
2.0 ARCHITECTURE |
75 |
============================================================================== |
76 |
|
77 |
------------------- |
78 |
Directory Locations |
79 |
------------------- |
80 |
The system requires two directories, one serving up the public interfaces |
81 |
(hereafter referred to as "public libdata"), another which contains the |
82 |
administration and authoring side ("administration libdata"). General |
83 |
directory path information and other variables may be customized in a file |
84 |
named global_vars.php. This file must be in either (a) BOTH the libdata |
85 |
administration directory AND public directory or (b) in the Apache-defined |
86 |
default location for server-side inclusions. Check with Apache and/or PHP |
87 |
documentation for this. The "include" subdirectory below the libdata |
88 |
administration directory must always be a subdirectory below administration |
89 |
and cannot currently be renamed or moved without some program modifications. |
90 |
(Although an additional Apache-defined include location for the |
91 |
global_vars.php file may be anywhere). Following is an example arrangement: |
92 |
|
93 |
----------------- |
94 |
Public interfaces |
95 |
----------------- |
96 |
/htdocs/www/libdata |
97 |
|
98 |
/images |
99 |
/styles |
100 |
|
101 |
------------------------- |
102 |
Administration interfaces |
103 |
------------------------- |
104 |
/htdocs/www-ssl/libdata_admin |
105 |
|
106 |
/docs |
107 |
/images |
108 |
/include |
109 |
/install (but should be moved elsewhere at installation Step #11.) |
110 |
|
111 |
Note that the administration side here is piped through SSL (defined by |
112 |
Apache configuration). It is strongly recommended that this portion be |
113 |
SSL, since authentication and user management is handled by this portion |
114 |
of libdata. At the very least, a self-signed server certificate can be |
115 |
created without cost. If this is not possible, then the public and |
116 |
administration portions will likely need to have separate directory names |
117 |
(for example "libdata" for public and "libdata_admin" for the |
118 |
administrative side). |
119 |
|
120 |
If your site is not configured for SSL, you need it only for LibData, and don't |
121 |
mind staff having to click (one time) that they trust the server certificate, |
122 |
following are instructions for creating a self-signed certificate on Red Hat 9: |
123 |
http://www.redhat.com/docs/manuals/linux/RHL-9-Manual/custom-guide/s1-secureserver-overview-certs.html |
124 |
|
125 |
Another good tool, usable for other Linux distributions (tested on SuSE) is TinyCA: |
126 |
http://tinyca.sm-zone.net/ |
127 |
|
128 |
============================================================================== |
129 |
3.0 INSTALLATION PROCEDURES |
130 |
============================================================================== |
131 |
|
132 |
------------- |
133 |
Initial Setup |
134 |
------------- |
135 |
Before proceeding to the installation steps, it is necessary to configure |
136 |
PHP and Apache for the following: |
137 |
|
138 |
(A) Interpretation of both .php and .phtml file extensions by the php |
139 |
engine. Changing this setting may vary depending on your installation. |
140 |
(try /etc/httpd/conf.d/php.conf with a default Red Hat 9 install). |
141 |
(B) "register_globals = On" should be set in your php.ini file |
142 |
(try /etc/php.ini with a default Red Hat 9 install). |
143 |
(C) Also in the php.ini file, make sure that "magic_quotes_gpc = Off". |
144 |
gpc stands for get/post/cookie, and turning quote escaping on will |
145 |
create problems for interaction at various layers between HTML, |
146 |
PHP and mySQL. Things might get double-escaped, or not escaped at all |
147 |
depending on whether the string has made the "round trip" to the client |
148 |
and back, has been stored in mySQL, etc. Many PHP developers recommend |
149 |
turning this setting off, and LibData has been created with this in mind. |
150 |
If it must be turned on, some work will need to be done with textInmySQL() |
151 |
and textSearchmySQL() to prevent mySQL errors in app_controls.php. |
152 |
(D) Apache SSL should be +StdEnvVars for the SSL port. |
153 |
(E) Server side Includes should be allowed. |
154 |
|
155 |
There may be additional tweaking based on your installation, but the |
156 |
previous are minimal required settings. |
157 |
|
158 |
------------------------------------------- |
159 |
(1) Build the Public LibData html directory |
160 |
------------------------------------------- |
161 |
LibData comes as two tar.gz files. One is named libdata_pos10.tar.gz. This tar |
162 |
contains all of the public HTML and PHP code. The "p" in this package refers to |
163 |
the "public" side of LibData. Extracting the tar will produce a libdata_pos |
164 |
directory which should be moved to a web-servable location on an Apache instance. |
165 |
|
166 |
It's possible to rename this directory to something more meaningful to your |
167 |
installation. Whatever directory name is chosen, changes will need to be made in |
168 |
Step #3 below to configure LibData to recognize it. It's possible to rename the |
169 |
directories after LibData is fully populated with data as well -- the directory names |
170 |
are hardcoded in only two configuration files (refer to Step #3 for further details). |
171 |
Changes are simple and go into effect immediately. So feel free to rename libdata_pos |
172 |
to something more useful. |
173 |
|
174 |
The public and administration portions of LibData should have different directory names. |
175 |
It's possible (but not recommended) to give them the same name if the public portion |
176 |
is in a directory like /www/html/libdata and the administrative resides in |
177 |
/www-ssl/html/libdata. But to avoid confusion, we recommend giving them differing names |
178 |
(as well as locations). |
179 |
|
180 |
File permissions and ownerships should be tweaked by a unix administrator as necessary. |
181 |
|
182 |
--------------------------------------------------- |
183 |
(2) Build the Administration LibData html directory |
184 |
--------------------------------------------------- |
185 |
The administration/staff modules are contained in libdata_aos10.tar.gz. The "a" in |
186 |
this package refers to the "administrative" side of LibData. Extracting the tar will |
187 |
produce a libdata_aos directory which should be moved to a web-servable location on |
188 |
an Apache instance as with the previous step -- but it is HIGHLY recommended that this |
189 |
directory be in an SSL-served location, since passwords and other information will |
190 |
be sent. |
191 |
|
192 |
As with the public portion of LibData, it's possible to rename this directory to something |
193 |
more meaningful to your installation. Whatever directory name is chosen, changes will |
194 |
need to be made in Step #3 below to configure LibData to recognize it. |
195 |
|
196 |
File permissions and ownerships should be tweaked by a unix administrator as necessary. |
197 |
|
198 |
------------------------- |
199 |
(3) Tweak global_vars.php |
200 |
------------------------- |
201 |
There are initially two instances of this file in your installation, one in |
202 |
libdata administration and one in libdata public. The files are initially |
203 |
identical, and should always remain identical. Note that with PHP it's possible to |
204 |
configure a single, default server-side include location. So it's possible to |
205 |
delete one of these files, and move the other to that default include location. |
206 |
(This requires minor tweaking with Apache and/or PHP configuration files.) But it's |
207 |
recommended that you worry about this later -- first get LibData up and running. |
208 |
|
209 |
Both files should be tweaked with information specific to your installation: |
210 |
server name, LibData "system" name, administrator, e-mail contact information, etc. and |
211 |
installation directory locations. Take note of the existing format -- don't add or subtract |
212 |
trailing backslashes. These files can be tweaked any time, during installation or with a |
213 |
fully-populated production LibData. Changes go into effect immediately, there is no |
214 |
process to start/restart, etc. |
215 |
|
216 |
-------------------------------------------------------------------- |
217 |
(4) Create the LibData databases (libdata, libstats, and libsession) |
218 |
-------------------------------------------------------------------- |
219 |
|
220 |
*** IMPORTANT **************************************************************** |
221 |
Ensure that there are NO existing mysql databases named libdata, libstats or |
222 |
libsession on your server. They will be DESTROYED by the next step. Running |
223 |
the following script will DROP all existing LibData references to ensure a |
224 |
clean install of the databases, a base data set, and mysql users. |
225 |
****************************************************************************** |
226 |
|
227 |
Refer to the install directory in the libdata administrative directory. |
228 |
|
229 |
Run the script named libload.pl, and follow the instructions given. The script |
230 |
must be run on the server hosting the mySQL daemon, and the mySQL root account is |
231 |
probably the best account for this procedure to avoid access denial issues (though |
232 |
if you have another account capable of dropping/adding databases, making changes |
233 |
to the mysql grants table, and reloading/refreshing them, you may use that account |
234 |
instead.) |
235 |
|
236 |
If you encounter errors with this step, double-check the following: |
237 |
(1) The location of the Perl interpreter might not necessarily be |
238 |
/usr/bin/perl. Please adjust the reference in libload.pl accordingly. |
239 |
Also, make sure that this file is executable (chmod +x libload.pl). |
240 |
(1) The mySQL daemon is initialized (for the first time), up and running. |
241 |
(2) The mySQL account/password you supplied is valid, and may access the |
242 |
mySQL command-line client with permissions required to perform the actions |
243 |
listed above. |
244 |
|
245 |
Keep this script in a secure place, since it contains default mySQL password |
246 |
information. Don't re-run it without backing up all data in libdata, libstats, |
247 |
or libsession that you wish to keep! |
248 |
|
249 |
---------------------------------- |
250 |
(5) Examine mySQL user permissions |
251 |
---------------------------------- |
252 |
This section refers to the "users" that PHP will use in interacting with mySQL |
253 |
LibData databases (libdata, libstats, and libsession). These users are not to |
254 |
be confused with authoring staff accounts -- those are maintained from within |
255 |
LibData itself (and not stored here). |
256 |
|
257 |
Use whichever mySQL client you are most comfortable with (command-line, |
258 |
phpMyAdmin, mySQL Control Center, etc.) access the internal "mysql" |
259 |
system database. |
260 |
|
261 |
In the mysql.user table the libload.pl script (previous step above) |
262 |
should have created basic libdata and libsession accounts with the following |
263 |
rights. Following is an example of entering the rows manually: |
264 |
|
265 |
INSERT INTO user VALUES (%, libdata, password(t0ught0gu355), |
266 |
Y, Y, Y, Y, Y, N, N, N,N,N,N,N,N,N); |
267 |
|
268 |
INSERT INTO user VALUES (%, libsession, password(3v3nt0gh3r), |
269 |
N, N, N, N, N, N, N, N,N,N,N,N,N,N); |
270 |
|
271 |
Changing passwords is trivial, and you ought to do it now. For example: |
272 |
|
273 |
UPDATE user SET password = password('newpass') WHERE user = 'libdata'; |
274 |
UPDATE user SET password = password('anotherone') WHERE user = 'libsession'; |
275 |
|
276 |
Passwords must be 6 characters minimum or you won't be able to login -- LibData's |
277 |
login mechanism will reject even valid passwords under 6 characters in length. |
278 |
Be sure to pick a mixture of numbers and letters, and alternate some upper and |
279 |
lower case. |
280 |
|
281 |
*** Note that making changes to the libdata or libsession usernames or |
282 |
passwords will require corresponding changes in the following files (the |
283 |
username and password must match what exists in the database). Since they |
284 |
appear as plain-text in the PHP scripts below, you'll need to manage |
285 |
unix user access to these files carefully: |
286 |
|
287 |
LibData administration path: |
288 |
include/db_connect.php (general libdata database connection include) |
289 |
include/accessClass.php (determines access rights for current user session) |
290 |
include/sessionClass.php (creates, logs out, and validates existing sessions) |
291 |
|
292 |
LibData public path: |
293 |
db_connect.php (general libdata database connection include) |
294 |
|
295 |
*** Changing the passwords on a scheduled basis is recommended, and should |
296 |
be part of an overall security plan. In fact, they should be changed |
297 |
immediately after installing LibData for the first time so you're not |
298 |
running with default and/or known passwords. |
299 |
|
300 |
Note that this may be tweaked further. If, for example, the PHP scripts will |
301 |
be run on the same server as the mySQL daemon, then the "%" in the first column |
302 |
might be tweaked to include only that DNS/IP address. This prevents the accounts |
303 |
from accessing your mySQL server from any other server. The usernames themselves |
304 |
(libdata and libsession) may also be renamed, so long as changes are made in the |
305 |
files discussed in this section (and the next). But this isn't recommended at |
306 |
this stage. LibData may be fine-tuned later -- and there's little reason to |
307 |
rename them anyway. |
308 |
|
309 |
----------------------------------------------- |
310 |
(6) Examine the mySQL database/host permissions |
311 |
----------------------------------------------- |
312 |
Next, the db table should be examined with regard to similar issues. Following |
313 |
is an example of a manual INSERT into them: |
314 |
|
315 |
INSERT INTO db VALUES (%, libdata, libdata, |
316 |
Y, Y, N, N, N, N, N, N, N, N); |
317 |
|
318 |
INSERT INTO db VALUES (%, libsession, libsession, |
319 |
Y, Y, Y, Y, N, N, N, N, N, N); |
320 |
|
321 |
Again, note that the initial "%" in the first column might be tweaked to |
322 |
limit to your particular hostname. |
323 |
|
324 |
If you've renamed the libdata and libsession users (Step #5 above) you'll need |
325 |
to rename them here as well. |
326 |
|
327 |
------------------------------------ |
328 |
(7) Configure PHP connection strings |
329 |
------------------------------------ |
330 |
If you've made any changes to the libdata and libsession mysql user passwords |
331 |
(hopefully you have!) double-check that they are correctly referenced in the |
332 |
following files. |
333 |
|
334 |
You'll also need to change the database server strings in each of the files, |
335 |
with an IP or valid DNS name appropriate to the server hosting your instance |
336 |
of LibData: |
337 |
|
338 |
LibData administration path: |
339 |
include/db_connect.php (general libdata database connection include) |
340 |
include/accessClass.php (determines access rights for current user session) |
341 |
include/sessionClass.php (creates, destroys and validates existing session) |
342 |
|
343 |
LibData public path: |
344 |
db_connect.php (general libdata database connection include) |
345 |
|
346 |
--------------------------------------------------------- |
347 |
(8) Reload and refresh the mySQL grant/permissions tables |
348 |
--------------------------------------------------------- |
349 |
The mySQL grants tables should be refreshed and reloaded at this point. This |
350 |
can be done with the mySQL command-line client by the following: |
351 |
|
352 |
mysqladmin -uroot -p reload |
353 |
mysqladmin -uroot -p refresh |
354 |
|
355 |
After each command, it will prompt for the administrator password. |
356 |
|
357 |
------------------------------------------------------------------------- |
358 |
(9) The LibData system should now be functional. Login as administrator. |
359 |
------------------------------------------------------------------------- |
360 |
|
361 |
Go to the appropriate url and login as administrator. For example: |
362 |
https://www.yourlibrary.edu/libdata/login.phtml |
363 |
|
364 |
default username: admin |
365 |
default password: libd4t4 |
366 |
|
367 |
(a) Follow the Manager Functions link on the bottom of the main console. |
368 |
(b) Click "Manage Staff List" toward the bottom of the Manager Function menu. |
369 |
(c) Select the radio button for Administrator, System to edit that account. |
370 |
(d) Change the administrator password as desired. |
371 |
|
372 |
Note that passwords, for security purposes, are never displayed on any of |
373 |
the forms. Also, passwords for any account may be purged to NULL. This |
374 |
automatically renders the account inactive (accounts without passwords may |
375 |
not login). Be sure not to purge the password for the admin account, or |
376 |
you'll need to enter mySQL manually, and execute a query like this: |
377 |
|
378 |
UPDATE libdata.staff SET password = password('n3wpassw0rd') WHERE staff_id = 2; |
379 |
|
380 |
or some such in order to log back into the system as administrator. |
381 |
|
382 |
------------------------------------- |
383 |
(10) Test mySQL single-quote escaping |
384 |
------------------------------------- |
385 |
|
386 |
Assuming your LibData configuration is now functional, it's a good idea to |
387 |
test proper escaping of single-quotes. Refer to 3.0 Installation Initial Setup |
388 |
Part C above on why magic_quotes_gpc must be turned Off. |
389 |
|
390 |
On the main authoring console, attempt to enter a resource with a |
391 |
single-quote in the title. For example, Here's a Title. (Also note |
392 |
that it is necessary to select some initial master subject or master |
393 |
information type -- refer to the user manuals on why this functionality |
394 |
was programmed). Pick anything from either drop-down box. Failing to |
395 |
do this will result in a screen why it's (as currently programmed) necessary |
396 |
to pick something. |
397 |
|
398 |
On the resource entry form, click the Save New Resource button. Then |
399 |
update the title field by pulling out the quote, for example: Heres a Title. |
400 |
Click the Update Resource button. Then go back and add the single-quote one |
401 |
more time, Here's a Title. Click Update Resource again. This will test both |
402 |
the insert and update SQL functionality. |
403 |
|
404 |
If LibData reports a SQL error, you'll most likely need to make a tweak here: |
405 |
{libdata admin directory location} include/app_controls.php. |
406 |
|
407 |
Look for the function textInmySQL(). Most all of LibData's mySQL insert/update |
408 |
strings are first routed here. Depending on your mySQL installation, you may need |
409 |
to escape single-quotes with a backslash as follows: \'. Comment out the |
410 |
appropriate pass-through ($outgoing = $incoming) and uncomment the nearby line |
411 |
above which performs this escaping. Add additional filters here as desired, |
412 |
and make sure that the corresponding data entry form (generally stored in |
413 |
include/forms.php) calls this function for each form field to be filtered for |
414 |
input or update. |
415 |
|
416 |
Make the same corresponding change in textSearchmySQL(). This function is |
417 |
virtually identical, but filters text going into (some) SELECT type statements. |
418 |
Both textInmySQL() and textSearchmySQL() might be heavily modified for |
419 |
bullet-proofing, possible security issues, etc. |
420 |
|
421 |
Note that double-quotes typically present a problem on many sites in all but |
422 |
textarea based HTML form fields. Future versions of LibData may resolve this |
423 |
(but there are some limits with using HTML as an interface). |
424 |
|
425 |
One possible mechanism is to convert certain special characters to their HTML hex |
426 |
equivalents with textInmySQL. The next step, then, is to convert all search-entry |
427 |
forms into their hex equivalents as well, so that converted data is searched with |
428 |
user input in the identical syntax. |
429 |
|
430 |
Other variants on these themes use the php functions addslashes() stripslashes(). |
431 |
Hopefully everything will be working and you won't need to go down this path. |
432 |
|
433 |
------------ |
434 |
(11) Cleanup |
435 |
------------ |
436 |
|
437 |
Assuming you've successfully installed LibData, this step is important from a |
438 |
security standpoint and should not be neglected. |
439 |
|
440 |
Move the install subdirectory (currently in the administrative directory) out of |
441 |
a web-servable location, and give only root users access to it -- since re-running |
442 |
the install script will DROP a production/populated LibData database. It's also |
443 |
recommended to chmod -x libload.pl to prevent a malicious user (or yourself) from |
444 |
making a grave mistake to a production system... |
445 |
|
446 |
------------------------------ |
447 |
Tips for further configuration |
448 |
------------------------------ |
449 |
(1) The public libdata db_connect.php may, alternatively, use a different |
450 |
username/password from the administrative db_connect.php file. Furthermore, |
451 |
the public connection need have only SELECT rights to the libdata database, |
452 |
and SELECT and INSERT rights to the libstats database. There are several |
453 |
strategies to fine-tune security, some of them are related uniquely to your |
454 |
institution and levels of paranoia. |
455 |
|
456 |
============================================================================== |
457 |
4.0 TROUBLESHOOTING |
458 |
============================================================================== |
459 |
|
460 |
Debugging dynamic database applications offers challenges on several fronts. |
461 |
If there are problems using LibData at this point, there are four main areas |
462 |
to debug: |
463 |
|
464 |
(1) Unix. Are the libdata files set to the appropriate permissions? Are |
465 |
they web servable? |
466 |
|
467 |
(2) Apache. Is it serving up other web basic HTML pages? |
468 |
|
469 |
(3) PHP. Create basic "Hello World" PHP programs with both a .php and .phtml |
470 |
extension. Are they being served? Make sure that Apache is configured to |
471 |
interpret both .PHP and .PHTML pages through the PHP parser. Make sure that |
472 |
the global_vars.php files, in both the public and administration locations, |
473 |
have the correct information. It sometimes helps to run the phpinfo() function |
474 |
(visit the http://www.php.net site for documentation on it) for information |
475 |
gathering purposes. Make sure that PHP is configured with register_globals = On |
476 |
(refer to 3.0 Installation Procedures Initial Setup Step A.). To test if this |
477 |
is functioning properly, write a simple .PHTML page with a form POST method to |
478 |
another .PHTML page. Collect the value on the recipient .PHTML page and simply |
479 |
print it for display. If it comes through properly, then a problem with |
480 |
variable passing may be ruled out. |
481 |
|
482 |
(4) mySQL. Make sure that the affected files (db_connect.php on both the |
483 |
public and administration sides, accessClass.php, and sessionClass.php) all |
484 |
have information which corresponds to the name of your mySQL server, and the |
485 |
correct mySQL accounts and passwords. Ensure that those accounts have the |
486 |
appropriate permissions as specified in the installation procedures above. |
487 |
Be sure to refresh and reload the mySQL grant tables also as specified, since |
488 |
some changes to the grants table do not always go into effect immediately. |
489 |
|
490 |
For installation problems or suggestions, please send an e-mail to the |
491 |
developer, Paul Bramscher (brams006@umn.edu), though neither I nor the |
492 |
University of Minnesota can make any guarantees or warranties with regard to |
493 |
LibData or its support. |
494 |
|
495 |
|
496 |
|
497 |
November 21, 2003 |
498 |
Paul F. Bramscher |
499 |
brams006@umn.edu |
500 |
University of Minnesota Libraries |